@thallylabs/migrate 0.2.3 → 0.2.5
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 +25 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +738 -118
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
// src/repository.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import {
|
|
4
|
-
existsSync as
|
|
5
|
-
lstatSync as
|
|
6
|
-
readFileSync as
|
|
4
|
+
existsSync as existsSync4,
|
|
5
|
+
lstatSync as lstatSync4,
|
|
6
|
+
readFileSync as readFileSync4,
|
|
7
7
|
readdirSync
|
|
8
8
|
} from "fs";
|
|
9
|
-
import { basename, dirname as
|
|
9
|
+
import { basename, dirname as dirname3, extname as extname4, relative as relative4, resolve as resolvePath } from "path";
|
|
10
10
|
|
|
11
|
-
// src/
|
|
11
|
+
// src/components.ts
|
|
12
|
+
import { createHash } from "crypto";
|
|
12
13
|
import { existsSync, lstatSync, readFileSync } from "fs";
|
|
13
|
-
import { extname,
|
|
14
|
-
import
|
|
14
|
+
import { dirname, extname, relative as relative2, resolve as resolve2 } from "path";
|
|
15
|
+
import remarkMdx from "remark-mdx";
|
|
16
|
+
import remarkParse from "remark-parse";
|
|
17
|
+
import ts from "typescript";
|
|
18
|
+
import { unified } from "unified";
|
|
19
|
+
|
|
20
|
+
// src/frontmatter.ts
|
|
15
21
|
import { parse as parseYaml } from "yaml";
|
|
22
|
+
function parseFrontmatter(raw) {
|
|
23
|
+
const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
24
|
+
const opening = /^---([^\r\n]*)\r?\n/.exec(source);
|
|
25
|
+
if (!opening || opening[1].startsWith("-")) return { content: source, data: {} };
|
|
26
|
+
const language = opening[1].trim().toLowerCase();
|
|
27
|
+
const remainder = source.slice(opening[0].length);
|
|
28
|
+
const closing = /^---[ \t]*\r?$/m.exec(remainder);
|
|
29
|
+
const matter = closing ? remainder.slice(0, closing.index) : remainder;
|
|
30
|
+
let content = closing ? remainder.slice(closing.index + closing[0].length) : "";
|
|
31
|
+
if (content.startsWith("\r\n")) content = content.slice(2);
|
|
32
|
+
else if (content.startsWith("\n")) content = content.slice(1);
|
|
33
|
+
if (matter.trim() === "" || !["", "yaml", "yml"].includes(language)) {
|
|
34
|
+
return { content, data: {} };
|
|
35
|
+
}
|
|
36
|
+
const parsed = parseYaml(matter);
|
|
37
|
+
return {
|
|
38
|
+
content,
|
|
39
|
+
data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
16
42
|
|
|
17
43
|
// src/path.ts
|
|
18
44
|
import { isAbsolute, relative, resolve, sep } from "path";
|
|
@@ -51,6 +77,18 @@ function pageIdFromReference(value, preserveCase = false) {
|
|
|
51
77
|
const segments = baseSegments.map((segment) => slugifySegment(segment, preserveCase)).filter(Boolean);
|
|
52
78
|
return segments.join("/") || "introduction";
|
|
53
79
|
}
|
|
80
|
+
function mintlifyLocalizedReference(value, locale, defaultPageIds = /* @__PURE__ */ new Set()) {
|
|
81
|
+
const reference = value.replace(/^\/+/, "");
|
|
82
|
+
const localePrefix = `${locale.toLowerCase()}/`;
|
|
83
|
+
if (reference.toLowerCase().startsWith(localePrefix)) return reference.slice(localePrefix.length);
|
|
84
|
+
const sectionPrefix = `${locale.toLowerCase()}-`;
|
|
85
|
+
if (reference.toLowerCase().startsWith(sectionPrefix)) {
|
|
86
|
+
const candidate = reference.slice(sectionPrefix.length);
|
|
87
|
+
const candidateId = pageIdFromReference(candidate, true);
|
|
88
|
+
if (candidateId && defaultPageIds.has(candidateId)) return candidate;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
54
92
|
function resolveWithin(root, candidate) {
|
|
55
93
|
if (isAbsolute(candidate) || candidate.includes("\0")) {
|
|
56
94
|
throw new Error(`Unsafe migration path: ${candidate}`);
|
|
@@ -70,7 +108,471 @@ function normalizeAssetPath(value) {
|
|
|
70
108
|
return segments.join("/");
|
|
71
109
|
}
|
|
72
110
|
|
|
111
|
+
// src/components.ts
|
|
112
|
+
var parser = unified().use(remarkParse).use(remarkMdx);
|
|
113
|
+
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs"]);
|
|
114
|
+
var DATA_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".css", ".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".woff", ".woff2"]);
|
|
115
|
+
var SHARED_IMPORTS = /* @__PURE__ */ new Set(["react", "react/jsx-runtime", "react/jsx-dev-runtime"]);
|
|
116
|
+
var REACT_GLOBALS = /* @__PURE__ */ new Set([
|
|
117
|
+
"useState",
|
|
118
|
+
"useEffect",
|
|
119
|
+
"useLayoutEffect",
|
|
120
|
+
"useMemo",
|
|
121
|
+
"useCallback",
|
|
122
|
+
"useRef",
|
|
123
|
+
"useReducer",
|
|
124
|
+
"useContext",
|
|
125
|
+
"useId",
|
|
126
|
+
"useTransition",
|
|
127
|
+
"useDeferredValue",
|
|
128
|
+
"useImperativeHandle",
|
|
129
|
+
"useSyncExternalStore",
|
|
130
|
+
"useInsertionEffect",
|
|
131
|
+
"useOptimistic",
|
|
132
|
+
"useActionState",
|
|
133
|
+
"use",
|
|
134
|
+
"createContext",
|
|
135
|
+
"forwardRef",
|
|
136
|
+
"memo"
|
|
137
|
+
]);
|
|
138
|
+
var MAX_COMPONENT_FILES = 300;
|
|
139
|
+
var MAX_COMPONENT_BYTES = 2e7;
|
|
140
|
+
var MAX_FILE_BYTES = 2e6;
|
|
141
|
+
function applyReplacements(source, replacements) {
|
|
142
|
+
return replacements.sort((a, b) => b.start - a.start).reduce(
|
|
143
|
+
(text, edit) => text.slice(0, edit.start) + edit.value + text.slice(edit.end),
|
|
144
|
+
source
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function hash(value) {
|
|
148
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
149
|
+
}
|
|
150
|
+
function portableSpecifier(path) {
|
|
151
|
+
return path.replace(/\.tsx?$/, "");
|
|
152
|
+
}
|
|
153
|
+
function sourceFile(source, filename) {
|
|
154
|
+
return ts.createSourceFile(
|
|
155
|
+
filename,
|
|
156
|
+
source,
|
|
157
|
+
ts.ScriptTarget.Latest,
|
|
158
|
+
true,
|
|
159
|
+
/\.[cm]?ts$/.test(filename) ? ts.ScriptKind.TS : ts.ScriptKind.TSX
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function walk(node, visitor) {
|
|
163
|
+
visitor(node);
|
|
164
|
+
for (const child of node.children ?? []) walk(child, visitor);
|
|
165
|
+
}
|
|
166
|
+
function implicitReactImports(source) {
|
|
167
|
+
const bindings = /* @__PURE__ */ new Set();
|
|
168
|
+
const references = /* @__PURE__ */ new Set();
|
|
169
|
+
function bind(name) {
|
|
170
|
+
if (ts.isIdentifier(name)) bindings.add(name.text);
|
|
171
|
+
else for (const element of name.elements) if (ts.isBindingElement(element)) bind(element.name);
|
|
172
|
+
}
|
|
173
|
+
for (const statement of source.statements) {
|
|
174
|
+
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
175
|
+
const clause = statement.importClause;
|
|
176
|
+
if (clause.name) bindings.add(clause.name.text);
|
|
177
|
+
if (clause.namedBindings) {
|
|
178
|
+
if (ts.isNamespaceImport(clause.namedBindings)) bindings.add(clause.namedBindings.name.text);
|
|
179
|
+
else for (const element of clause.namedBindings.elements) bindings.add(element.name.text);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) bind(declaration.name);
|
|
183
|
+
if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) bindings.add(statement.name.text);
|
|
184
|
+
}
|
|
185
|
+
function visit(node) {
|
|
186
|
+
if (ts.isIdentifier(node) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) && !(ts.isPropertyAssignment(node.parent) && node.parent.name === node)) references.add(node.text);
|
|
187
|
+
ts.forEachChild(node, visit);
|
|
188
|
+
}
|
|
189
|
+
visit(source);
|
|
190
|
+
const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
|
|
191
|
+
return [
|
|
192
|
+
references.has("React") && !bindings.has("React") ? "import * as React from 'react';" : "",
|
|
193
|
+
hooks.length ? `import { ${hooks.join(", ")} } from 'react';` : ""
|
|
194
|
+
].filter(Boolean).join("\n");
|
|
195
|
+
}
|
|
196
|
+
function imports(statement) {
|
|
197
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) return [];
|
|
198
|
+
const clause = statement.importClause;
|
|
199
|
+
if (clause.isTypeOnly) return [];
|
|
200
|
+
const source = statement.moduleSpecifier.text;
|
|
201
|
+
const bindings = clause.name ? [{ local: clause.name.text, imported: "default", source }] : [];
|
|
202
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
203
|
+
for (const element of clause.namedBindings.elements) {
|
|
204
|
+
if (!element.isTypeOnly) bindings.push({ local: element.name.text, imported: element.propertyName?.text ?? element.name.text, source });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return bindings;
|
|
208
|
+
}
|
|
209
|
+
function createComponentMigrator(siteRoot, warnings, sourceIdentity) {
|
|
210
|
+
const root = resolve2(siteRoot);
|
|
211
|
+
const destinationRoot = `src/mdx/migrated/${hash(sourceIdentity)}`;
|
|
212
|
+
const copied = /* @__PURE__ */ new Map();
|
|
213
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
214
|
+
let copiedBytes = 0;
|
|
215
|
+
function warn(message, source) {
|
|
216
|
+
warnings.push({ code: "unsupported-config", message, source: relative2(root, source).replace(/\\/g, "/") });
|
|
217
|
+
}
|
|
218
|
+
function checkedFile(path) {
|
|
219
|
+
const local = relative2(root, path);
|
|
220
|
+
resolveWithin(root, local);
|
|
221
|
+
let current = root;
|
|
222
|
+
if (lstatSync(current).isSymbolicLink()) throw new Error("symbolic links are not imported");
|
|
223
|
+
for (const segment of local.split(/[\\/]/)) {
|
|
224
|
+
current = resolveWithin(current, segment);
|
|
225
|
+
if (lstatSync(current).isSymbolicLink()) throw new Error("symbolic links are not imported");
|
|
226
|
+
}
|
|
227
|
+
if (!lstatSync(path).isFile()) throw new Error("dependency is not a regular file");
|
|
228
|
+
return path;
|
|
229
|
+
}
|
|
230
|
+
function resolveDependency(specifier, importer) {
|
|
231
|
+
if (specifier.includes("\\") || specifier.includes("\0") || /[?#]/.test(specifier)) throw new Error("unsupported component dependency path");
|
|
232
|
+
const candidate = specifier.startsWith("/") ? resolveWithin(root, specifier.slice(1)) : resolveWithin(root, relative2(root, resolve2(dirname(importer), specifier)));
|
|
233
|
+
const candidates = [candidate];
|
|
234
|
+
if (!extname(candidate)) {
|
|
235
|
+
candidates.push(...[".tsx", ".jsx", ".ts", ".js", ".mjs", ".json"].map((extension) => candidate + extension));
|
|
236
|
+
candidates.push(...["index.tsx", "index.jsx", "index.ts", "index.js"].map((name) => resolve2(candidate, name)));
|
|
237
|
+
} else if (extname(candidate) === ".js") {
|
|
238
|
+
candidates.push(candidate.slice(0, -3) + ".ts", candidate.slice(0, -3) + ".tsx");
|
|
239
|
+
}
|
|
240
|
+
for (const path of candidates) {
|
|
241
|
+
if (existsSync(path) && lstatSync(path).isFile()) return checkedFile(path);
|
|
242
|
+
}
|
|
243
|
+
throw new Error(`component dependency ${specifier} was not found`);
|
|
244
|
+
}
|
|
245
|
+
function outputPath(path) {
|
|
246
|
+
return `${destinationRoot}/source/${relative2(root, path).replace(/\\/g, "/")}`;
|
|
247
|
+
}
|
|
248
|
+
function copyGraph(entry) {
|
|
249
|
+
const staged = /* @__PURE__ */ new Map();
|
|
250
|
+
let stagedBytes = 0;
|
|
251
|
+
function visit(path) {
|
|
252
|
+
const destination = outputPath(path);
|
|
253
|
+
if (copied.has(destination) || staged.has(destination)) return;
|
|
254
|
+
checkedFile(path);
|
|
255
|
+
const size = lstatSync(path).size;
|
|
256
|
+
if (size > MAX_FILE_BYTES || copiedBytes + stagedBytes + size > MAX_COMPONENT_BYTES || copied.size + staged.size >= MAX_COMPONENT_FILES) throw new Error("component migration budget exceeded");
|
|
257
|
+
const extension = extname(path).toLowerCase();
|
|
258
|
+
if (!CODE_EXTENSIONS.has(extension) && !DATA_EXTENSIONS.has(extension)) throw new Error(`unsupported dependency file type ${extension}`);
|
|
259
|
+
stagedBytes += size;
|
|
260
|
+
const content = readFileSync(path);
|
|
261
|
+
staged.set(destination, { path: destination, content });
|
|
262
|
+
if (!CODE_EXTENSIONS.has(extension)) {
|
|
263
|
+
if (extension === ".css" && /@import\b|url\s*\(/i.test(content.toString("utf8"))) throw new Error("CSS resource dependencies require manual migration");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const text = content.toString("utf8");
|
|
267
|
+
const syntax = ts.transpileModule(text, {
|
|
268
|
+
fileName: path,
|
|
269
|
+
reportDiagnostics: true,
|
|
270
|
+
compilerOptions: { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext }
|
|
271
|
+
});
|
|
272
|
+
if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
|
|
273
|
+
const ast = sourceFile(text, path);
|
|
274
|
+
if (ast.statements.some((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression) && statement.expression.text === "use server")) throw new Error("server-only component modules require manual migration");
|
|
275
|
+
const edits = [];
|
|
276
|
+
function dependency(literal) {
|
|
277
|
+
const specifier = literal.text;
|
|
278
|
+
if (SHARED_IMPORTS.has(specifier)) return;
|
|
279
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
280
|
+
throw new Error(`external package ${specifier} requires manual installation and review`);
|
|
281
|
+
}
|
|
282
|
+
const target = resolveDependency(specifier, path);
|
|
283
|
+
visit(target);
|
|
284
|
+
const nextPath = relative2(dirname(destination), outputPath(target)).replace(/\\/g, "/");
|
|
285
|
+
edits.push({ start: literal.getStart(ast), end: literal.end, value: JSON.stringify(portableSpecifier(nextPath.startsWith(".") ? nextPath : `./${nextPath}`)) });
|
|
286
|
+
}
|
|
287
|
+
function inspect(node) {
|
|
288
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) dependency(node.moduleSpecifier);
|
|
289
|
+
if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require")) {
|
|
290
|
+
const argument = node.arguments[0];
|
|
291
|
+
if (node.arguments.length !== 1 || !argument || !ts.isStringLiteralLike(argument)) throw new Error("computed component imports require manual migration");
|
|
292
|
+
dependency(argument);
|
|
293
|
+
}
|
|
294
|
+
ts.forEachChild(node, inspect);
|
|
295
|
+
}
|
|
296
|
+
inspect(ast);
|
|
297
|
+
staged.set(destination, { path: destination, content: `'use client';
|
|
298
|
+
|
|
299
|
+
${implicitReactImports(ast)}
|
|
300
|
+
${applyReplacements(text, edits)}` });
|
|
301
|
+
}
|
|
302
|
+
visit(entry);
|
|
303
|
+
for (const [path, file] of staged) copied.set(path, file);
|
|
304
|
+
copiedBytes += stagedBytes;
|
|
305
|
+
return outputPath(entry);
|
|
306
|
+
}
|
|
307
|
+
function register(path, imported) {
|
|
308
|
+
const name = `Migrated${hash(`${path}:${imported}`)}`;
|
|
309
|
+
registrations.set(name, { path, imported });
|
|
310
|
+
return name;
|
|
311
|
+
}
|
|
312
|
+
function transform(raw, currentFile) {
|
|
313
|
+
const content = parseFrontmatter(raw).content;
|
|
314
|
+
const frontmatter = raw.slice(0, raw.length - content.length);
|
|
315
|
+
let tree;
|
|
316
|
+
try {
|
|
317
|
+
tree = parser.parse(content);
|
|
318
|
+
} catch {
|
|
319
|
+
warn("Custom component analysis could not parse this MDX; its source was preserved for manual migration.", currentFile);
|
|
320
|
+
return raw;
|
|
321
|
+
}
|
|
322
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
323
|
+
const edits = [];
|
|
324
|
+
const declarations = [];
|
|
325
|
+
const moduleImports = [];
|
|
326
|
+
const sharedImportEdits = [];
|
|
327
|
+
let hasUnsupportedImports = false;
|
|
328
|
+
function hasExpressionReference(names, options = {}) {
|
|
329
|
+
let found = false;
|
|
330
|
+
function inspect2(node) {
|
|
331
|
+
if (ts.isIdentifier(node) && names.has(node.text)) found = true;
|
|
332
|
+
ts.forEachChild(node, inspect2);
|
|
333
|
+
}
|
|
334
|
+
walk(tree, (node) => {
|
|
335
|
+
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;
|
|
336
|
+
if (node.name && [...names].some((name) => node.name.startsWith(`${name}.`) || options.includeDirectTags && node.name === name)) found = true;
|
|
337
|
+
if (options.includeDeclarations !== false && node.type === "mdxjsEsm" && node.value) {
|
|
338
|
+
for (const statement of sourceFile(node.value, "expression.tsx").statements) {
|
|
339
|
+
if (!ts.isImportDeclaration(statement)) inspect2(statement);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const expressions = [
|
|
343
|
+
...["mdxFlowExpression", "mdxTextExpression"].includes(node.type) ? [node.value ?? ""] : [],
|
|
344
|
+
...(node.attributes ?? []).flatMap((attribute) => {
|
|
345
|
+
if (attribute.type === "mdxJsxExpressionAttribute" && typeof attribute.value === "string") return [attribute.value];
|
|
346
|
+
return attribute.value && typeof attribute.value === "object" ? [attribute.value.value ?? ""] : [];
|
|
347
|
+
})
|
|
348
|
+
];
|
|
349
|
+
for (const expression of expressions) inspect2(sourceFile(expression, "expression.tsx"));
|
|
350
|
+
});
|
|
351
|
+
return found;
|
|
352
|
+
}
|
|
353
|
+
for (const node of tree.children ?? []) {
|
|
354
|
+
if (node.type !== "mdxjsEsm" || node.value === void 0 || node.position?.start.offset === void 0) continue;
|
|
355
|
+
const ast = sourceFile(node.value, "inline.tsx");
|
|
356
|
+
for (const statement of ast.statements) {
|
|
357
|
+
if (!ts.isImportDeclaration(statement)) {
|
|
358
|
+
let inspect2 = function(node2) {
|
|
359
|
+
if (ts.isCallExpression(node2) && (node2.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node2.expression) && node2.expression.text === "require")) unsupportedDependency = true;
|
|
360
|
+
ts.forEachChild(node2, inspect2);
|
|
361
|
+
};
|
|
362
|
+
var inspect = inspect2;
|
|
363
|
+
let unsupportedDependency = ts.isExportDeclaration(statement) && !!statement.moduleSpecifier;
|
|
364
|
+
inspect2(statement);
|
|
365
|
+
if (unsupportedDependency) {
|
|
366
|
+
hasUnsupportedImports = true;
|
|
367
|
+
warn("Inline MDX module dependencies require manual extraction; source was preserved.", currentFile);
|
|
368
|
+
}
|
|
369
|
+
declarations.push({
|
|
370
|
+
start: node.position.start.offset + statement.getStart(ast),
|
|
371
|
+
end: node.position.start.offset + statement.end,
|
|
372
|
+
source: statement.getText(ast)
|
|
373
|
+
});
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const bindings = imports(statement);
|
|
377
|
+
const specifier = ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
378
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
379
|
+
if (SHARED_IMPORTS.has(specifier)) {
|
|
380
|
+
moduleImports.push(statement.getText(ast));
|
|
381
|
+
sharedImportEdits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
382
|
+
} else {
|
|
383
|
+
hasUnsupportedImports = true;
|
|
384
|
+
warn(`MDX import ${specifier} requires manual package installation or registration; the import was preserved.`, currentFile);
|
|
385
|
+
}
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (/\.mdx?$/.test(specifier)) continue;
|
|
389
|
+
if (!bindings.length || statement.importClause?.namedBindings && ts.isNamespaceImport(statement.importClause.namedBindings)) {
|
|
390
|
+
warn("Namespace or side-effect MDX imports require manual registration; the import was preserved.", currentFile);
|
|
391
|
+
hasUnsupportedImports = true;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (bindings.some((binding) => !/^[A-Z]/.test(binding.local))) {
|
|
395
|
+
warn("MDX imports containing non-component values require manual migration; the import was preserved.", currentFile);
|
|
396
|
+
hasUnsupportedImports = true;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (hasExpressionReference(new Set(bindings.map((binding) => binding.local)))) {
|
|
400
|
+
warn("Imported components used in MDX expressions, component props, or member tags require manual migration; the import was preserved.", currentFile);
|
|
401
|
+
hasUnsupportedImports = true;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
const path = copyGraph(resolveDependency(specifier, currentFile));
|
|
406
|
+
for (const binding of bindings) {
|
|
407
|
+
const name = register(path, binding.imported);
|
|
408
|
+
aliases.set(binding.local, name);
|
|
409
|
+
moduleImports.push(`import { ${binding.imported} as ${binding.local} } from ${JSON.stringify(portableSpecifier(`./${relative2(destinationRoot, path).replace(/\\/g, "/")}`))};`);
|
|
410
|
+
}
|
|
411
|
+
edits.push({ start: node.position.start.offset + statement.getStart(ast), end: node.position.start.offset + statement.end, value: "" });
|
|
412
|
+
} catch (error) {
|
|
413
|
+
hasUnsupportedImports = true;
|
|
414
|
+
warn(`Custom component was preserved for manual migration: ${error instanceof Error ? error.message : "unsupported dependency"}.`, currentFile);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const extracted = [];
|
|
419
|
+
for (const candidate of tree.children ?? []) {
|
|
420
|
+
const node = candidate.type === "paragraph" && candidate.children?.length === 1 ? candidate.children[0] : candidate;
|
|
421
|
+
if (!["mdxJsxFlowElement", "mdxJsxTextElement"].includes(node.type) || node.position?.start.offset === void 0 || node.position.end.offset === void 0) continue;
|
|
422
|
+
let interactive = false;
|
|
423
|
+
let supported = true;
|
|
424
|
+
walk(node, (child) => {
|
|
425
|
+
if (child.attributes?.some((attribute) => /^on[A-Z]/.test(attribute.name ?? ""))) interactive = true;
|
|
426
|
+
if (child.name && /^[A-Z]/.test(child.name) && !aliases.has(child.name)) supported = false;
|
|
427
|
+
if (!["mdxJsxFlowElement", "mdxJsxTextElement", "mdxFlowExpression", "mdxTextExpression", "text", "paragraph"].includes(child.type)) supported = false;
|
|
428
|
+
});
|
|
429
|
+
if (!interactive) continue;
|
|
430
|
+
if (!supported || hasUnsupportedImports) {
|
|
431
|
+
warn("Interactive MDX containing Markdown or unregistered components requires manual extraction; source was preserved.", currentFile);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
const jsx = content.slice(node.position.start.offset, node.position.end.offset);
|
|
435
|
+
const result = ts.transpileModule(`export default function Inline() { return (${jsx}); }`, {
|
|
436
|
+
fileName: "inline.jsx",
|
|
437
|
+
compilerOptions: { jsx: ts.JsxEmit.Preserve },
|
|
438
|
+
reportDiagnostics: true
|
|
439
|
+
});
|
|
440
|
+
if (result.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) {
|
|
441
|
+
warn("Interactive MDX could not be safely extracted as JSX; source was preserved.", currentFile);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
extracted.push({ node, name: `Inline${extracted.length}`, jsx });
|
|
445
|
+
}
|
|
446
|
+
if (extracted.length && declarations.length) {
|
|
447
|
+
let bind2 = function(name) {
|
|
448
|
+
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
449
|
+
else for (const element of name.elements) if (ts.isBindingElement(element)) bind2(element.name);
|
|
450
|
+
};
|
|
451
|
+
var bind = bind2;
|
|
452
|
+
const declared = /* @__PURE__ */ new Set();
|
|
453
|
+
for (const declaration of declarations) {
|
|
454
|
+
for (const statement of sourceFile(declaration.source, "declaration.tsx").statements) {
|
|
455
|
+
if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) declared.add(statement.name.text);
|
|
456
|
+
if (ts.isVariableStatement(statement)) for (const variable of statement.declarationList.declarations) bind2(variable.name);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const hasSharedDeclarations = hasExpressionReference(declared, {
|
|
460
|
+
excludedNodes: extracted.map((entry) => entry.node),
|
|
461
|
+
includeDeclarations: false,
|
|
462
|
+
includeDirectTags: true
|
|
463
|
+
});
|
|
464
|
+
if (hasSharedDeclarations) {
|
|
465
|
+
warn("Interactive MDX shares declarations with remaining page content; source was preserved for manual extraction.", currentFile);
|
|
466
|
+
extracted.length = 0;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (extracted.length && (copied.size >= MAX_COMPONENT_FILES || Buffer.byteLength(content) > MAX_FILE_BYTES || copiedBytes + Buffer.byteLength(content) > MAX_COMPONENT_BYTES)) {
|
|
470
|
+
warn("Interactive MDX exceeded the component migration budget; source was preserved.", currentFile);
|
|
471
|
+
extracted.length = 0;
|
|
472
|
+
}
|
|
473
|
+
if (extracted.length) {
|
|
474
|
+
const path = `${destinationRoot}/inline-${hash(relative2(root, currentFile))}.jsx`;
|
|
475
|
+
let declarationSource = [...new Set(declarations.map((entry) => entry.source))].join("\n\n");
|
|
476
|
+
declarationSource = declarationSource.replace(
|
|
477
|
+
/document\.getElementById\(\s*(['"])search-bar-entry\1\s*\)\.click\(\s*\)/g,
|
|
478
|
+
`document.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', ctrlKey: true, bubbles: true }))`
|
|
479
|
+
);
|
|
480
|
+
const inlineSource = [
|
|
481
|
+
"'use client';",
|
|
482
|
+
"",
|
|
483
|
+
...moduleImports,
|
|
484
|
+
"",
|
|
485
|
+
declarationSource,
|
|
486
|
+
"",
|
|
487
|
+
...extracted.map(({ name, jsx }) => `export function ${name}() {
|
|
488
|
+
return (${jsx});
|
|
489
|
+
}`),
|
|
490
|
+
""
|
|
491
|
+
].join("\n");
|
|
492
|
+
copied.set(path, { path, content: `'use client';
|
|
493
|
+
${implicitReactImports(sourceFile(inlineSource, "inline.jsx"))}
|
|
494
|
+
${inlineSource}` });
|
|
495
|
+
copiedBytes += Buffer.byteLength(inlineSource);
|
|
496
|
+
for (const { node, name } of extracted) {
|
|
497
|
+
edits.push({ start: node.position.start.offset, end: node.position.end.offset, value: `<${register(path, name)} />` });
|
|
498
|
+
}
|
|
499
|
+
edits.push(...declarations.map(({ start, end }) => ({ start, end, value: "" })), ...sharedImportEdits);
|
|
500
|
+
}
|
|
501
|
+
walk(tree, (node) => {
|
|
502
|
+
const replacement = node.name ? aliases.get(node.name) : void 0;
|
|
503
|
+
if (!replacement || node.position?.start.offset === void 0 || node.position.end.offset === void 0) return;
|
|
504
|
+
const start = node.position.start.offset;
|
|
505
|
+
const end = node.position.end.offset;
|
|
506
|
+
if (extracted.some((item) => start >= item.node.position.start.offset && end <= item.node.position.end.offset)) return;
|
|
507
|
+
const text = content.slice(start, end);
|
|
508
|
+
const opening = text.indexOf(`<${node.name}`);
|
|
509
|
+
if (opening >= 0) edits.push({ start: start + opening + 1, end: start + opening + 1 + node.name.length, value: replacement });
|
|
510
|
+
const closing = text.lastIndexOf(`</${node.name}`);
|
|
511
|
+
if (closing >= 0) edits.push({ start: start + closing + 2, end: start + closing + 2 + node.name.length, value: replacement });
|
|
512
|
+
});
|
|
513
|
+
return frontmatter + applyReplacements(content, edits);
|
|
514
|
+
}
|
|
515
|
+
function files() {
|
|
516
|
+
if (!registrations.size) return [];
|
|
517
|
+
const entries = [...registrations.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
518
|
+
return [...copied.values(), {
|
|
519
|
+
path: "src/mdx/custom-components.tsx",
|
|
520
|
+
content: [
|
|
521
|
+
"/** Repository components preserved by migration; this registry is customer-owned. */",
|
|
522
|
+
"import type { MDXComponents } from 'mdx/types'",
|
|
523
|
+
...entries.map(([name, binding]) => {
|
|
524
|
+
const path = portableSpecifier(`./${relative2("src/mdx", binding.path).replace(/\\/g, "/")}`);
|
|
525
|
+
return `import { ${binding.imported} as ${name} } from ${JSON.stringify(path)}`;
|
|
526
|
+
}),
|
|
527
|
+
"",
|
|
528
|
+
"export const customComponents: MDXComponents = {",
|
|
529
|
+
...entries.map(([name]) => ` ${name},`),
|
|
530
|
+
"}",
|
|
531
|
+
""
|
|
532
|
+
].join("\n")
|
|
533
|
+
}];
|
|
534
|
+
}
|
|
535
|
+
return { transform, files };
|
|
536
|
+
}
|
|
537
|
+
function mergeComponentRegistry(existing, incoming) {
|
|
538
|
+
const fingerprint = hash(incoming);
|
|
539
|
+
const name = `MigratedRegistry${fingerprint}`;
|
|
540
|
+
const registryPath = `./migrated-components-${fingerprint}`;
|
|
541
|
+
const source = sourceFile(existing, "custom-components.tsx");
|
|
542
|
+
const alreadyImported = source.statements.some((statement) => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
|
|
543
|
+
let initializer;
|
|
544
|
+
for (const statement of source.statements) {
|
|
545
|
+
if (!ts.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue;
|
|
546
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
547
|
+
if (ts.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
while (initializer && (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer) || ts.isParenthesizedExpression(initializer))) initializer = initializer.expression;
|
|
551
|
+
if (!initializer || !ts.isObjectLiteralExpression(initializer)) {
|
|
552
|
+
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.");
|
|
553
|
+
}
|
|
554
|
+
const content = alreadyImported ? existing : applyReplacements(existing, [
|
|
555
|
+
{
|
|
556
|
+
start: source.statements.filter((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
|
|
557
|
+
end: source.statements.filter((statement) => ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
|
|
558
|
+
value: `
|
|
559
|
+
import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
|
|
560
|
+
`
|
|
561
|
+
},
|
|
562
|
+
{ start: initializer.getStart(source) + 1, end: initializer.getStart(source) + 1, value: `
|
|
563
|
+
...${name},` }
|
|
564
|
+
]);
|
|
565
|
+
return [
|
|
566
|
+
{ path: `src/mdx/migrated-components-${fingerprint}.tsx`, content: incoming },
|
|
567
|
+
{ path: "src/mdx/custom-components.tsx", content }
|
|
568
|
+
];
|
|
569
|
+
}
|
|
570
|
+
|
|
73
571
|
// src/docusaurus.ts
|
|
572
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
573
|
+
import { extname as extname2, posix } from "path";
|
|
574
|
+
import JSON5 from "json5";
|
|
575
|
+
import { parse as parseYaml2 } from "yaml";
|
|
74
576
|
var MAX_CONFIG_BYTES = 1e6;
|
|
75
577
|
var CATEGORY_FILENAMES = ["_category_.json", "_category_.yml", "_category_.yaml"];
|
|
76
578
|
var SIDEBAR_FILENAMES = [
|
|
@@ -184,10 +686,10 @@ function rewriteDocusaurusLinks(body, current, descriptors) {
|
|
|
184
686
|
}).join("\n");
|
|
185
687
|
}
|
|
186
688
|
function readBoundedText(path) {
|
|
187
|
-
if (
|
|
689
|
+
if (lstatSync2(path).size > MAX_CONFIG_BYTES) {
|
|
188
690
|
throw new Error("Docusaurus sidebar config exceeded the 1 MB static-parser limit.");
|
|
189
691
|
}
|
|
190
|
-
return
|
|
692
|
+
return readFileSync2(path, "utf8");
|
|
191
693
|
}
|
|
192
694
|
function matchingObjectLiteral(source, start) {
|
|
193
695
|
let depth = 0;
|
|
@@ -342,33 +844,33 @@ function matchingArrayLiteral(source, start) {
|
|
|
342
844
|
function configuredSidebarPath(repositoryRoot) {
|
|
343
845
|
for (const filename of ["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"]) {
|
|
344
846
|
const path = resolveWithin(repositoryRoot, filename);
|
|
345
|
-
if (!
|
|
847
|
+
if (!existsSync2(path)) continue;
|
|
346
848
|
const source = readBoundedText(path);
|
|
347
849
|
const match = source.match(/\bsidebarPath\s*:\s*(?:require\.resolve\(\s*)?(['"])([^'"]+)\1/);
|
|
348
850
|
if (!match) continue;
|
|
349
851
|
const candidate = match[2].replace(/^\.\//, "");
|
|
350
852
|
const resolved = resolveWithin(repositoryRoot, candidate);
|
|
351
|
-
if (
|
|
853
|
+
if (existsSync2(resolved) && lstatSync2(resolved).isFile()) return candidate;
|
|
352
854
|
}
|
|
353
855
|
return null;
|
|
354
856
|
}
|
|
355
857
|
function readDocusaurusSidebars(repositoryRoot) {
|
|
356
858
|
const configured = configuredSidebarPath(repositoryRoot);
|
|
357
|
-
const sourcePath = [configured, ...SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) =>
|
|
859
|
+
const sourcePath = [configured, ...SIDEBAR_FILENAMES].filter((value) => Boolean(value)).find((candidate) => existsSync2(resolveWithin(repositoryRoot, candidate)));
|
|
358
860
|
if (!sourcePath) return null;
|
|
359
861
|
const absolutePath = resolveWithin(repositoryRoot, sourcePath);
|
|
360
862
|
const source = readBoundedText(absolutePath);
|
|
361
|
-
const parsed =
|
|
863
|
+
const parsed = extname2(sourcePath).toLowerCase() === ".json" ? objectValue(JSON5.parse(source)) : parseStaticSidebarModule(source);
|
|
362
864
|
if (!parsed) throw new Error("Docusaurus sidebar config must export an object.");
|
|
363
865
|
return { config: parsed, sourcePath };
|
|
364
866
|
}
|
|
365
867
|
function readCategoryMetadata(contentRoot, directory) {
|
|
366
868
|
for (const filename of CATEGORY_FILENAMES) {
|
|
367
869
|
const path = resolveWithin(contentRoot, posix.join(directory, filename));
|
|
368
|
-
if (!
|
|
870
|
+
if (!existsSync2(path) || !lstatSync2(path).isFile()) continue;
|
|
369
871
|
try {
|
|
370
872
|
const raw = readBoundedText(path);
|
|
371
|
-
const parsed = filename.endsWith(".json") ? JSON5.parse(raw) :
|
|
873
|
+
const parsed = filename.endsWith(".json") ? JSON5.parse(raw) : parseYaml2(raw);
|
|
372
874
|
const object = objectValue(parsed);
|
|
373
875
|
if (!object) return {};
|
|
374
876
|
return {
|
|
@@ -533,31 +1035,39 @@ function projectDocusaurusNavigation(input) {
|
|
|
533
1035
|
};
|
|
534
1036
|
}
|
|
535
1037
|
|
|
536
|
-
// src/frontmatter.ts
|
|
537
|
-
import matter from "gray-matter";
|
|
538
|
-
var FRONTMATTER_OPTIONS = {
|
|
539
|
-
engines: {
|
|
540
|
-
javascript: () => ({}),
|
|
541
|
-
js: () => ({})
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
function parseFrontmatter(raw) {
|
|
545
|
-
return matter(raw, FRONTMATTER_OPTIONS);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
1038
|
// src/mdx.ts
|
|
1039
|
+
import remarkMdx2 from "remark-mdx";
|
|
1040
|
+
import remarkParse2 from "remark-parse";
|
|
1041
|
+
import { unified as unified2 } from "unified";
|
|
549
1042
|
function titleFromId(id) {
|
|
550
1043
|
const value = id.split("/").at(-1) ?? id;
|
|
551
1044
|
if (value === "introduction") return "Introduction";
|
|
552
1045
|
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
553
1046
|
}
|
|
1047
|
+
var descriptionParser = unified2().use(remarkParse2).use(remarkMdx2);
|
|
554
1048
|
function firstParagraph(content) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
|
|
1049
|
+
function plainText(node) {
|
|
1050
|
+
if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
|
|
1051
|
+
if (node.type === "image") return node.alt ?? "";
|
|
1052
|
+
if (node.type === "break") return " ";
|
|
1053
|
+
return (node.children ?? []).map(plainText).join("");
|
|
1054
|
+
}
|
|
1055
|
+
function findParagraph(node) {
|
|
1056
|
+
if (node.type === "paragraph") {
|
|
1057
|
+
const value = plainText(node).replace(/\s+/g, " ").trim();
|
|
1058
|
+
if (value) return value.slice(0, 240);
|
|
1059
|
+
}
|
|
1060
|
+
for (const child of node.children ?? []) {
|
|
1061
|
+
const value = findParagraph(child);
|
|
1062
|
+
if (value) return value;
|
|
1063
|
+
}
|
|
1064
|
+
return "";
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
return findParagraph(descriptionParser.parse(content));
|
|
1068
|
+
} catch {
|
|
1069
|
+
return "";
|
|
559
1070
|
}
|
|
560
|
-
return "";
|
|
561
1071
|
}
|
|
562
1072
|
function docusaurusAdmonitionTag(kind) {
|
|
563
1073
|
if (kind === "danger") return "Error";
|
|
@@ -635,24 +1145,94 @@ function parseMarkdownPage(input) {
|
|
|
635
1145
|
}
|
|
636
1146
|
|
|
637
1147
|
// src/navigation.ts
|
|
638
|
-
import { existsSync as
|
|
639
|
-
import { dirname, extname as
|
|
1148
|
+
import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync } from "fs";
|
|
1149
|
+
import { dirname as dirname2, extname as extname3, relative as relative3 } from "path";
|
|
640
1150
|
var MAX_MINTLIFY_CONFIG_BYTES = 2e6;
|
|
641
1151
|
function addMintlifyDirectoryRedirects(config, pages) {
|
|
642
|
-
const pageIds = new Set(pages.
|
|
1152
|
+
const pageIds = new Set(pages.map((page) => page.id));
|
|
643
1153
|
const redirects = [...config.redirects ?? []];
|
|
644
1154
|
const redirectSources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
1155
|
+
function hasAuthoredRedirect(source) {
|
|
1156
|
+
if (redirectSources.has(source)) return true;
|
|
1157
|
+
return (config.redirects ?? []).some((redirect) => {
|
|
1158
|
+
const match = redirect.source.match(/^(.*)\/:[A-Za-z_][A-Za-z0-9_]*\*\/?$/);
|
|
1159
|
+
return match ? source === match[1] || source.startsWith(`${match[1]}/`) : false;
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
645
1162
|
for (const page of pages) {
|
|
646
|
-
if (
|
|
647
|
-
const parent = page.
|
|
1163
|
+
if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
|
|
1164
|
+
const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
|
|
648
1165
|
if (!parent || pageIds.has(parent)) continue;
|
|
649
1166
|
const source = `/${parent}`;
|
|
650
|
-
if (
|
|
651
|
-
redirects.push({ source, destination: `/${page.
|
|
1167
|
+
if (hasAuthoredRedirect(source)) continue;
|
|
1168
|
+
redirects.push({ source, destination: `/${page.id}`, permanent: false });
|
|
1169
|
+
redirectSources.add(source);
|
|
1170
|
+
}
|
|
1171
|
+
const navigationOrder = [];
|
|
1172
|
+
function collectPages(nodes) {
|
|
1173
|
+
for (const node of nodes) {
|
|
1174
|
+
if (typeof node === "string") navigationOrder.push(node);
|
|
1175
|
+
else if (!node.hidden) collectPages(node.pages);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
for (const tab of config.tabs) {
|
|
1179
|
+
if (!tab.hidden) collectPages([...tab.pages ?? [], ...tab.groups ?? []]);
|
|
1180
|
+
}
|
|
1181
|
+
const pagesByNavigationId = /* @__PURE__ */ new Map();
|
|
1182
|
+
for (const page of pages) {
|
|
1183
|
+
if (page.hidden) continue;
|
|
1184
|
+
const variants = pagesByNavigationId.get(page.navigationId) ?? [];
|
|
1185
|
+
variants.push(page);
|
|
1186
|
+
pagesByNavigationId.set(page.navigationId, variants);
|
|
1187
|
+
}
|
|
1188
|
+
const firstDescendantByDirectory = /* @__PURE__ */ new Map();
|
|
1189
|
+
for (const navigationId of navigationOrder) {
|
|
1190
|
+
for (const page of pagesByNavigationId.get(navigationId) ?? []) {
|
|
1191
|
+
const segments = page.id.split("/");
|
|
1192
|
+
for (let length = 1; length < segments.length; length++) {
|
|
1193
|
+
const directory = segments.slice(0, length).join("/");
|
|
1194
|
+
if (!firstDescendantByDirectory.has(directory)) firstDescendantByDirectory.set(directory, page);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
for (const redirect of config.redirects ?? []) {
|
|
1199
|
+
const destination = redirect.destination.split(/[?#]/, 1)[0].replace(/\/:[A-Za-z_][A-Za-z0-9_]*\*\/?$/, "");
|
|
1200
|
+
if (!destination.startsWith("/") || destination.startsWith("//") || /[:*()[\]{}]/.test(destination)) continue;
|
|
1201
|
+
const directory = trimEdgeSlashes(destination);
|
|
1202
|
+
if (!directory || pageIds.has(directory)) continue;
|
|
1203
|
+
const source = `/${directory}`;
|
|
1204
|
+
if (hasAuthoredRedirect(source)) continue;
|
|
1205
|
+
const landing = firstDescendantByDirectory.get(directory);
|
|
1206
|
+
if (!landing) continue;
|
|
1207
|
+
redirects.push({ source, destination: `/${landing.id}`, permanent: false });
|
|
652
1208
|
redirectSources.add(source);
|
|
653
1209
|
}
|
|
654
1210
|
return redirects.length > 0 ? { ...config, redirects } : config;
|
|
655
1211
|
}
|
|
1212
|
+
function addMintlifyHomepageRedirects(config, pages) {
|
|
1213
|
+
const defaultPages = new Set(pages.filter((page) => !page.locale || page.locale === config.i18n?.defaultLocale).map((page) => page.navigationId));
|
|
1214
|
+
function firstPage(nodes) {
|
|
1215
|
+
for (const node of nodes) {
|
|
1216
|
+
if (typeof node === "string") {
|
|
1217
|
+
if (defaultPages.has(node)) return node;
|
|
1218
|
+
} else if (!node.hidden) {
|
|
1219
|
+
const page = firstPage(node.pages);
|
|
1220
|
+
if (page) return page;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const homepage = config.tabs.filter((tab) => !tab.hidden).map((tab) => firstPage([...tab.pages ?? [], ...tab.groups ?? []])).find(Boolean);
|
|
1225
|
+
if (!homepage || homepage === "introduction") return config;
|
|
1226
|
+
const redirects = [...config.redirects ?? []];
|
|
1227
|
+
const sources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
1228
|
+
const roots = ["", ...(config.i18n?.locales ?? []).filter((locale) => locale.code !== config.i18n?.defaultLocale).map((locale) => locale.code)];
|
|
1229
|
+
for (const locale of roots) {
|
|
1230
|
+
const source = locale ? `/${locale}` : "/";
|
|
1231
|
+
if (sources.has(source)) continue;
|
|
1232
|
+
redirects.push({ source, destination: `${locale ? `/${locale}` : ""}/${homepage}`, permanent: false });
|
|
1233
|
+
}
|
|
1234
|
+
return { ...config, redirects };
|
|
1235
|
+
}
|
|
656
1236
|
var LANGUAGE_LABELS = {
|
|
657
1237
|
ar: "Arabic",
|
|
658
1238
|
de: "German",
|
|
@@ -706,16 +1286,16 @@ function jsonPointer(root, pointer) {
|
|
|
706
1286
|
}, root);
|
|
707
1287
|
}
|
|
708
1288
|
function readMintlifyJson(path, repositoryRoot) {
|
|
709
|
-
if (!
|
|
710
|
-
throw new Error(`Mintlify config reference is not a regular file: ${
|
|
1289
|
+
if (!existsSync3(path) || !lstatSync3(path).isFile()) {
|
|
1290
|
+
throw new Error(`Mintlify config reference is not a regular file: ${relative3(repositoryRoot, path)}`);
|
|
711
1291
|
}
|
|
712
1292
|
const realPath = realpathSync(path);
|
|
713
1293
|
const realRoot = realpathSync(repositoryRoot);
|
|
714
|
-
resolveWithin(realRoot,
|
|
715
|
-
if (
|
|
716
|
-
throw new Error(`Mintlify config reference exceeded ${MAX_MINTLIFY_CONFIG_BYTES / 1e6} MB: ${
|
|
1294
|
+
resolveWithin(realRoot, relative3(realRoot, realPath));
|
|
1295
|
+
if (lstatSync3(realPath).size > MAX_MINTLIFY_CONFIG_BYTES) {
|
|
1296
|
+
throw new Error(`Mintlify config reference exceeded ${MAX_MINTLIFY_CONFIG_BYTES / 1e6} MB: ${relative3(repositoryRoot, path)}`);
|
|
717
1297
|
}
|
|
718
|
-
return JSON.parse(
|
|
1298
|
+
return JSON.parse(readFileSync3(realPath, "utf8"));
|
|
719
1299
|
}
|
|
720
1300
|
function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
721
1301
|
if (Array.isArray(value)) {
|
|
@@ -725,8 +1305,8 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
725
1305
|
if (!object) return value;
|
|
726
1306
|
if (typeof object.$ref === "string") {
|
|
727
1307
|
const [filePart, pointer = ""] = object.$ref.split("#", 2);
|
|
728
|
-
const referencedFile = filePart ? resolveWithin(
|
|
729
|
-
const relativePath =
|
|
1308
|
+
const referencedFile = filePart ? resolveWithin(dirname2(currentFile), filePart) : currentFile;
|
|
1309
|
+
const relativePath = relative3(repositoryRoot, referencedFile);
|
|
730
1310
|
resolveWithin(repositoryRoot, relativePath);
|
|
731
1311
|
const stackKey = `${referencedFile}#${pointer}`;
|
|
732
1312
|
if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
|
|
@@ -751,7 +1331,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
751
1331
|
);
|
|
752
1332
|
}
|
|
753
1333
|
function readMintlifyConfig(repositoryRoot) {
|
|
754
|
-
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(
|
|
1334
|
+
const configPath = ["docs.json", "mint.json"].map((filename) => resolveWithin(repositoryRoot, filename)).find(existsSync3);
|
|
755
1335
|
if (!configPath) return null;
|
|
756
1336
|
const raw = readMintlifyJson(configPath, repositoryRoot);
|
|
757
1337
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
@@ -783,8 +1363,7 @@ function containerPresentation(value) {
|
|
|
783
1363
|
};
|
|
784
1364
|
}
|
|
785
1365
|
function registerReference(value, context) {
|
|
786
|
-
const
|
|
787
|
-
const localizedValue = localePrefix && value.replace(/^\/+/, "").startsWith(localePrefix) ? value.replace(/^\/+/, "").slice(localePrefix.length) : value;
|
|
1366
|
+
const localizedValue = context.locale ? mintlifyLocalizedReference(value, context.locale, context.defaultPageIds) : value;
|
|
788
1367
|
const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
|
|
789
1368
|
if (!navigationId) return null;
|
|
790
1369
|
const key = `${context.locale ?? ""}:${value}`;
|
|
@@ -1026,15 +1605,25 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1026
1605
|
const seenReferences = /* @__PURE__ */ new Set();
|
|
1027
1606
|
const warningKeys = /* @__PURE__ */ new Set();
|
|
1028
1607
|
const navigation = objectValue2(config.navigation) ?? config;
|
|
1029
|
-
const languages = Array.isArray(navigation.languages) ? navigation.languages.
|
|
1608
|
+
const languages = Array.isArray(navigation.languages) ? navigation.languages.flatMap((value) => {
|
|
1609
|
+
const language = objectValue2(value);
|
|
1610
|
+
if (!language) return [];
|
|
1611
|
+
try {
|
|
1612
|
+
const code = Intl.getCanonicalLocales(String(language.language ?? language.locale ?? "en"))[0];
|
|
1613
|
+
return [{ ...language, language: code }];
|
|
1614
|
+
} catch {
|
|
1615
|
+
warnings.push({ code: "unsupported-config", message: "A navigation language with an invalid locale code was skipped." });
|
|
1616
|
+
return [];
|
|
1617
|
+
}
|
|
1618
|
+
}) : [];
|
|
1030
1619
|
let tabs = [];
|
|
1031
1620
|
let i18n;
|
|
1032
1621
|
const projectionTrace = {};
|
|
1033
1622
|
if (languages.length > 0) {
|
|
1034
1623
|
const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
|
|
1035
|
-
const defaultLocale = String(defaultLanguage.language
|
|
1624
|
+
const defaultLocale = String(defaultLanguage.language);
|
|
1036
1625
|
const locales = languages.map((entry) => {
|
|
1037
|
-
const code = String(entry.language
|
|
1626
|
+
const code = String(entry.language);
|
|
1038
1627
|
let label = LANGUAGE_LABELS[code];
|
|
1039
1628
|
try {
|
|
1040
1629
|
label ??= new Intl.DisplayNames(["en"], { type: "language" }).of(code) ?? void 0;
|
|
@@ -1043,10 +1632,12 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1043
1632
|
return { code, label: label ?? code.toUpperCase() };
|
|
1044
1633
|
});
|
|
1045
1634
|
i18n = { defaultLocale, locales };
|
|
1046
|
-
|
|
1047
|
-
|
|
1635
|
+
const defaultPageIds = /* @__PURE__ */ new Set();
|
|
1636
|
+
for (const language of [defaultLanguage, ...languages.filter((entry) => entry !== defaultLanguage)]) {
|
|
1637
|
+
const locale = String(language.language);
|
|
1048
1638
|
const context = {
|
|
1049
1639
|
locale,
|
|
1640
|
+
defaultPageIds,
|
|
1050
1641
|
pathPrefix: options.pathPrefix,
|
|
1051
1642
|
references,
|
|
1052
1643
|
seenReferences,
|
|
@@ -1059,7 +1650,10 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1059
1650
|
"Documentation",
|
|
1060
1651
|
language === defaultLanguage ? projectionTrace : void 0
|
|
1061
1652
|
);
|
|
1062
|
-
if (language === defaultLanguage)
|
|
1653
|
+
if (language === defaultLanguage) {
|
|
1654
|
+
tabs = languageTabs;
|
|
1655
|
+
for (const reference of references) defaultPageIds.add(reference.navigationId);
|
|
1656
|
+
}
|
|
1063
1657
|
}
|
|
1064
1658
|
} else {
|
|
1065
1659
|
const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
|
|
@@ -1122,8 +1716,8 @@ function topLevelLabel(segment) {
|
|
|
1122
1716
|
function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(section)) {
|
|
1123
1717
|
const groups = /* @__PURE__ */ new Map();
|
|
1124
1718
|
for (const id of pageIds) {
|
|
1125
|
-
const
|
|
1126
|
-
const nestedSegment =
|
|
1719
|
+
const relative5 = id === "introduction" ? "" : id.startsWith(`${section}/`) ? id.slice(section.length + 1) : id;
|
|
1720
|
+
const nestedSegment = relative5.includes("/") ? relative5.split("/", 1)[0] : "overview";
|
|
1127
1721
|
const group = groups.get(nestedSegment) ?? [];
|
|
1128
1722
|
group.push(id);
|
|
1129
1723
|
groups.set(nestedSegment, group);
|
|
@@ -1207,7 +1801,7 @@ function buildNavigationFromPages(pages, options = {}) {
|
|
|
1207
1801
|
return { tabs: [{ tab: "Documentation", groups }] };
|
|
1208
1802
|
}
|
|
1209
1803
|
function isDocumentationExtension(filename) {
|
|
1210
|
-
return [".md", ".mdx", ".rst", ".txt"].includes(
|
|
1804
|
+
return [".md", ".mdx", ".rst", ".txt"].includes(extname3(filename).toLowerCase());
|
|
1211
1805
|
}
|
|
1212
1806
|
|
|
1213
1807
|
// src/repository.ts
|
|
@@ -1269,16 +1863,16 @@ var DOCUSAURUS_CONFIG_FILENAMES = [
|
|
|
1269
1863
|
function hasDocusaurusConfig(directory) {
|
|
1270
1864
|
return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
|
|
1271
1865
|
const path = resolveWithin(directory, filename);
|
|
1272
|
-
return
|
|
1866
|
+
return existsSync4(path) && lstatSync4(path).isFile();
|
|
1273
1867
|
});
|
|
1274
1868
|
}
|
|
1275
1869
|
function hasMintlifyConfig(directory) {
|
|
1276
1870
|
return ["docs.json", "mint.json"].some((filename) => {
|
|
1277
1871
|
const path = resolveWithin(directory, filename);
|
|
1278
|
-
if (!
|
|
1872
|
+
if (!existsSync4(path) || !lstatSync4(path).isFile()) return false;
|
|
1279
1873
|
if (filename === "mint.json") return true;
|
|
1280
1874
|
try {
|
|
1281
|
-
const config = JSON.parse(
|
|
1875
|
+
const config = JSON.parse(readFileSync4(path, "utf8"));
|
|
1282
1876
|
return String(config.$schema ?? "").includes("mintlify") || "navigation" in config;
|
|
1283
1877
|
} catch {
|
|
1284
1878
|
return false;
|
|
@@ -1292,7 +1886,7 @@ function findMintlifyProjectRoot(repositoryDir, docsDir) {
|
|
|
1292
1886
|
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
1293
1887
|
if (hasMintlifyConfig(path)) return path;
|
|
1294
1888
|
if (!candidate) break;
|
|
1295
|
-
const parent =
|
|
1889
|
+
const parent = dirname3(candidate);
|
|
1296
1890
|
candidate = parent === "." ? "" : parent;
|
|
1297
1891
|
}
|
|
1298
1892
|
}
|
|
@@ -1317,7 +1911,7 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
|
1317
1911
|
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
1318
1912
|
if (hasDocusaurusConfig(path)) return path;
|
|
1319
1913
|
if (!candidate) break;
|
|
1320
|
-
const parent =
|
|
1914
|
+
const parent = dirname3(candidate);
|
|
1321
1915
|
candidate = parent === "." ? "" : parent;
|
|
1322
1916
|
}
|
|
1323
1917
|
}
|
|
@@ -1339,9 +1933,9 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
|
1339
1933
|
return null;
|
|
1340
1934
|
}
|
|
1341
1935
|
function readDocusaurusConfigSource(projectRoot) {
|
|
1342
|
-
const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) =>
|
|
1343
|
-
if (!path ||
|
|
1344
|
-
return
|
|
1936
|
+
const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) => existsSync4(candidate) && lstatSync4(candidate).isFile());
|
|
1937
|
+
if (!path || lstatSync4(path).size > 1e6) return "";
|
|
1938
|
+
return readFileSync4(path, "utf8");
|
|
1345
1939
|
}
|
|
1346
1940
|
function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
1347
1941
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
@@ -1350,7 +1944,7 @@ function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
|
1350
1944
|
}
|
|
1351
1945
|
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
1352
1946
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
1353
|
-
const projectRelative =
|
|
1947
|
+
const projectRelative = relative4(repositoryDir, projectRoot).replace(/\\/g, "/");
|
|
1354
1948
|
const plugins = [];
|
|
1355
1949
|
const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
|
|
1356
1950
|
for (const match of source.matchAll(matcher)) {
|
|
@@ -1359,7 +1953,7 @@ function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
|
1359
1953
|
if (!localPath || !routePrefix) continue;
|
|
1360
1954
|
const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
|
|
1361
1955
|
const absolute = resolveWithin(repositoryDir, docsDir);
|
|
1362
|
-
if (!
|
|
1956
|
+
if (!existsSync4(absolute) || !lstatSync4(absolute).isDirectory()) continue;
|
|
1363
1957
|
plugins.push({ docsDir, routePrefix });
|
|
1364
1958
|
}
|
|
1365
1959
|
return plugins;
|
|
@@ -1402,7 +1996,7 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
1402
1996
|
const args = ["clone", "--depth", "1", "--single-branch"];
|
|
1403
1997
|
if (source.branch !== "HEAD") args.push("--branch", source.branch);
|
|
1404
1998
|
args.push("--", source.cloneUrl, targetDir);
|
|
1405
|
-
await new Promise((
|
|
1999
|
+
await new Promise((resolve3, reject) => {
|
|
1406
2000
|
const child = spawn("git", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1407
2001
|
let stderr = "";
|
|
1408
2002
|
child.stderr.setEncoding("utf8");
|
|
@@ -1411,7 +2005,7 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
1411
2005
|
});
|
|
1412
2006
|
child.on("error", reject);
|
|
1413
2007
|
child.on("close", (code) => {
|
|
1414
|
-
if (code === 0)
|
|
2008
|
+
if (code === 0) resolve3();
|
|
1415
2009
|
else reject(new Error(`Failed to clone ${source.owner}/${source.repo}: ${stderr.trim() || `git exited ${code}`}`));
|
|
1416
2010
|
});
|
|
1417
2011
|
});
|
|
@@ -1420,19 +2014,19 @@ function detectRepositoryPlatform(repositoryDir, docsDir) {
|
|
|
1420
2014
|
if (findMintlifyProjectRoot(repositoryDir, docsDir)) return "mintlify";
|
|
1421
2015
|
const selectedRoot = docsDir === void 0 ? repositoryDir : resolveWithin(repositoryDir, docsDir || ".");
|
|
1422
2016
|
const docsJson = resolveWithin(selectedRoot, "docs.json");
|
|
1423
|
-
if (
|
|
2017
|
+
if (existsSync4(docsJson)) {
|
|
1424
2018
|
try {
|
|
1425
|
-
const config = JSON.parse(
|
|
2019
|
+
const config = JSON.parse(readFileSync4(docsJson, "utf8"));
|
|
1426
2020
|
if (Array.isArray(config.tabs)) return "thally";
|
|
1427
2021
|
} catch {
|
|
1428
2022
|
}
|
|
1429
2023
|
}
|
|
1430
|
-
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) =>
|
|
2024
|
+
if (["docusaurus.config.js", "docusaurus.config.ts", "docusaurus.config.mjs"].some((name) => existsSync4(resolveWithin(repositoryDir, name)))) return "docusaurus";
|
|
1431
2025
|
if (findDocusaurusProjectRoot(repositoryDir)) return "docusaurus";
|
|
1432
|
-
if (
|
|
1433
|
-
if (
|
|
1434
|
-
if (["astro.config.mjs", "astro.config.ts"].some((name) =>
|
|
1435
|
-
if (["pages/_meta.json", "_meta.json"].some((name) =>
|
|
2026
|
+
if (existsSync4(resolveWithin(repositoryDir, "SUMMARY.md"))) return "gitbook";
|
|
2027
|
+
if (existsSync4(resolveWithin(repositoryDir, ".vitepress"))) return "vitepress";
|
|
2028
|
+
if (["astro.config.mjs", "astro.config.ts"].some((name) => existsSync4(resolveWithin(repositoryDir, name)))) return "starlight";
|
|
2029
|
+
if (["pages/_meta.json", "_meta.json"].some((name) => existsSync4(resolveWithin(repositoryDir, name)))) return "nextra";
|
|
1436
2030
|
return "unknown";
|
|
1437
2031
|
}
|
|
1438
2032
|
function containsMarkdown(directory, depth = 0) {
|
|
@@ -1440,7 +2034,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
1440
2034
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
1441
2035
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
1442
2036
|
const path = resolveWithin(directory, entry.name);
|
|
1443
|
-
if (entry.isFile() && [".md", ".mdx"].includes(
|
|
2037
|
+
if (entry.isFile() && [".md", ".mdx"].includes(extname4(entry.name).toLowerCase())) return true;
|
|
1444
2038
|
if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
|
|
1445
2039
|
}
|
|
1446
2040
|
return false;
|
|
@@ -1448,7 +2042,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
1448
2042
|
function detectRepositoryDocsDir(repositoryDir) {
|
|
1449
2043
|
for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
|
|
1450
2044
|
const path = resolveWithin(repositoryDir, candidate);
|
|
1451
|
-
if (
|
|
2045
|
+
if (existsSync4(path) && lstatSync4(path).isDirectory() && containsMarkdown(path)) return candidate;
|
|
1452
2046
|
}
|
|
1453
2047
|
return "";
|
|
1454
2048
|
}
|
|
@@ -1461,7 +2055,7 @@ function scanFiles(root) {
|
|
|
1461
2055
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
1462
2056
|
const path = resolveWithin(directory, entry.name);
|
|
1463
2057
|
if (entry.isDirectory()) visit(path);
|
|
1464
|
-
else if (entry.isFile()) files.push({ absolutePath: path, relativePath:
|
|
2058
|
+
else if (entry.isFile()) files.push({ absolutePath: path, relativePath: relative4(root, path).replace(/\\/g, "/") });
|
|
1465
2059
|
}
|
|
1466
2060
|
}
|
|
1467
2061
|
visit(root);
|
|
@@ -1516,9 +2110,9 @@ function interpolateSnippet(snippet, attributes) {
|
|
|
1516
2110
|
}).join("\n");
|
|
1517
2111
|
}
|
|
1518
2112
|
function resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot) {
|
|
1519
|
-
const repositoryRelative = sourcePath.startsWith("@site/") ?
|
|
2113
|
+
const repositoryRelative = sourcePath.startsWith("@site/") ? relative4(repositoryRoot, resolveWithin(siteRoot, sourcePath.slice("@site/".length))).replace(/\\/g, "/") : sourcePath.startsWith("/") ? relative4(repositoryRoot, resolveWithin(siteRoot, sourcePath.replace(/^\/+/, ""))).replace(/\\/g, "/") : relative4(repositoryRoot, resolvePath(dirname3(currentFile), sourcePath)).replace(/\\/g, "/");
|
|
1520
2114
|
const candidate = resolveWithin(repositoryRoot, repositoryRelative);
|
|
1521
|
-
resolveWithin(repositoryRoot,
|
|
2115
|
+
resolveWithin(repositoryRoot, relative4(repositoryRoot, candidate));
|
|
1522
2116
|
return candidate;
|
|
1523
2117
|
}
|
|
1524
2118
|
function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
@@ -1532,12 +2126,12 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
|
1532
2126
|
if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
|
|
1533
2127
|
}
|
|
1534
2128
|
for (const file of files) {
|
|
1535
|
-
if (![".md", ".mdx"].includes(
|
|
1536
|
-
const raw =
|
|
2129
|
+
if (![".md", ".mdx"].includes(extname4(file.relativePath).toLowerCase())) continue;
|
|
2130
|
+
const raw = readFileSync4(file.absolutePath, "utf8");
|
|
1537
2131
|
for (const match of raw.matchAll(matcher)) {
|
|
1538
2132
|
try {
|
|
1539
2133
|
const candidate = resolveSnippetPath(match[2], file.absolutePath, repositoryRoot, siteRoot);
|
|
1540
|
-
if (
|
|
2134
|
+
if (existsSync4(candidate) && lstatSync4(candidate).isFile()) {
|
|
1541
2135
|
aliases.set(match[1], candidate);
|
|
1542
2136
|
}
|
|
1543
2137
|
} catch {
|
|
@@ -1559,12 +2153,12 @@ function repositoryAssetHref(value, currentFile, siteRoot) {
|
|
|
1559
2153
|
} catch {
|
|
1560
2154
|
return null;
|
|
1561
2155
|
}
|
|
1562
|
-
if (!ASSET_EXTENSIONS.has(
|
|
2156
|
+
if (!ASSET_EXTENSIONS.has(extname4(decodedPath).toLowerCase())) return null;
|
|
1563
2157
|
try {
|
|
1564
|
-
const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(
|
|
1565
|
-
const siteRelative =
|
|
2158
|
+
const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(dirname3(currentFile), decodedPath);
|
|
2159
|
+
const siteRelative = relative4(siteRoot, candidate).replace(/\\/g, "/");
|
|
1566
2160
|
resolveWithin(siteRoot, siteRelative);
|
|
1567
|
-
if (!
|
|
2161
|
+
if (!existsSync4(candidate) || !lstatSync4(candidate).isFile()) return null;
|
|
1568
2162
|
const normalized = normalizeAssetPath(siteRelative);
|
|
1569
2163
|
if (!normalized) return null;
|
|
1570
2164
|
const rewritten = `/${normalized}${suffix}`;
|
|
@@ -1590,9 +2184,9 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1590
2184
|
(_statement, componentName, sourcePath) => {
|
|
1591
2185
|
try {
|
|
1592
2186
|
const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
|
|
1593
|
-
if (!
|
|
2187
|
+
if (!existsSync4(candidate) || !lstatSync4(candidate).isFile()) throw new Error("file not found");
|
|
1594
2188
|
const nested = inlineMdxSnippets(
|
|
1595
|
-
withoutFrontmatter(
|
|
2189
|
+
withoutFrontmatter(readFileSync4(candidate, "utf8")),
|
|
1596
2190
|
candidate,
|
|
1597
2191
|
repositoryRoot,
|
|
1598
2192
|
warnings,
|
|
@@ -1606,7 +2200,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1606
2200
|
warnings.push({
|
|
1607
2201
|
code: "missing-page",
|
|
1608
2202
|
message: `Imported snippet ${sourcePath} could not be resolved and was left as a comment.`,
|
|
1609
|
-
source:
|
|
2203
|
+
source: relative4(repositoryRoot, currentFile).replace(/\\/g, "/")
|
|
1610
2204
|
});
|
|
1611
2205
|
snippets.set(componentName, `{/* Missing imported snippet: ${sourcePath} */}`);
|
|
1612
2206
|
return "";
|
|
@@ -1616,7 +2210,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1616
2210
|
for (const [componentName, candidate] of globalAliases) {
|
|
1617
2211
|
if (snippets.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
|
|
1618
2212
|
const nested = inlineMdxSnippets(
|
|
1619
|
-
withoutFrontmatter(
|
|
2213
|
+
withoutFrontmatter(readFileSync4(candidate, "utf8")),
|
|
1620
2214
|
candidate,
|
|
1621
2215
|
repositoryRoot,
|
|
1622
2216
|
warnings,
|
|
@@ -1643,6 +2237,11 @@ function injectOpenApi(config, filename) {
|
|
|
1643
2237
|
else tabs.push({ tab: "API Reference", api: { source: `/${filename}` } });
|
|
1644
2238
|
return { ...config, tabs };
|
|
1645
2239
|
}
|
|
2240
|
+
function componentSourceIdentity(sourceUrl, repositoryDir, siteRoot) {
|
|
2241
|
+
const url = new URL(sourceUrl);
|
|
2242
|
+
const repository = url.hostname.toLowerCase() === "github.com" ? `https://github.com/${url.pathname.split("/").filter(Boolean).slice(0, 2).join("/").replace(/\.git$/i, "").toLowerCase()}` : `${url.origin}${trimTrailingSlashes(url.pathname)}`;
|
|
2243
|
+
return JSON.stringify([repository, relative4(repositoryDir, siteRoot).replace(/\\/g, "/")]);
|
|
2244
|
+
}
|
|
1646
2245
|
function migrateRepository(options) {
|
|
1647
2246
|
const repositoryDir = options.repositoryDir;
|
|
1648
2247
|
const platform = options.platform ?? detectRepositoryPlatform(repositoryDir, options.docsDir);
|
|
@@ -1652,21 +2251,23 @@ function migrateRepository(options) {
|
|
|
1652
2251
|
if (options.docsDir !== void 0) {
|
|
1653
2252
|
const selected = resolveWithin(repositoryDir, options.docsDir || ".");
|
|
1654
2253
|
if (platform === "docusaurus" && hasDocusaurusConfig(selected)) {
|
|
1655
|
-
const projectRelative =
|
|
2254
|
+
const projectRelative = relative4(repositoryDir, selected).replace(/\\/g, "/");
|
|
1656
2255
|
return [projectRelative, primaryDocusaurusDocsDirectory(selected)].filter(Boolean).join("/");
|
|
1657
2256
|
}
|
|
1658
2257
|
return options.docsDir;
|
|
1659
2258
|
}
|
|
1660
2259
|
if (docusaurusProjectRoot) {
|
|
1661
|
-
const projectRelative =
|
|
2260
|
+
const projectRelative = relative4(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
|
|
1662
2261
|
return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
|
|
1663
2262
|
}
|
|
1664
2263
|
if (platform === "mintlify" && mintlifyProjectRoot) {
|
|
1665
|
-
return
|
|
2264
|
+
return relative4(repositoryDir, mintlifyProjectRoot).replace(/\\/g, "/");
|
|
1666
2265
|
}
|
|
1667
2266
|
return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
|
|
1668
2267
|
})();
|
|
1669
2268
|
const warnings = [];
|
|
2269
|
+
const componentRoot = mintlifyProjectRoot ?? repositoryDir;
|
|
2270
|
+
const componentMigrator = platform === "mintlify" ? createComponentMigrator(componentRoot, warnings, componentSourceIdentity(options.sourceUrl, repositoryDir, componentRoot)) : void 0;
|
|
1670
2271
|
let docsConfig = { tabs: [] };
|
|
1671
2272
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
1672
2273
|
const exactReferenceMap = /* @__PURE__ */ new Map();
|
|
@@ -1683,9 +2284,10 @@ function migrateRepository(options) {
|
|
|
1683
2284
|
warnings.push(...projected.warnings);
|
|
1684
2285
|
for (const [index, reference] of projected.pageReferences.entries()) {
|
|
1685
2286
|
const key = normalizedReferenceKey(reference.ref);
|
|
1686
|
-
referenceMap.set(key, reference);
|
|
1687
|
-
|
|
1688
|
-
|
|
2287
|
+
if (!referenceMap.has(key)) referenceMap.set(key, reference);
|
|
2288
|
+
const exactKey = exactReferenceKey(reference.ref);
|
|
2289
|
+
if (!exactReferenceMap.has(exactKey)) exactReferenceMap.set(exactKey, reference);
|
|
2290
|
+
if (!referenceOrder.has(key)) referenceOrder.set(key, index);
|
|
1689
2291
|
}
|
|
1690
2292
|
}
|
|
1691
2293
|
} catch (error) {
|
|
@@ -1706,7 +2308,7 @@ function migrateRepository(options) {
|
|
|
1706
2308
|
}
|
|
1707
2309
|
}
|
|
1708
2310
|
const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
|
|
1709
|
-
if (!
|
|
2311
|
+
if (!existsSync4(contentRoot) || !lstatSync4(contentRoot).isDirectory()) {
|
|
1710
2312
|
throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
|
|
1711
2313
|
}
|
|
1712
2314
|
const files = scanFiles(contentRoot);
|
|
@@ -1717,6 +2319,8 @@ function migrateRepository(options) {
|
|
|
1717
2319
|
let skipped = 0;
|
|
1718
2320
|
let discovered = files.length;
|
|
1719
2321
|
const localeConfig = docsConfig.i18n;
|
|
2322
|
+
const defaultPageIds = new Set([...referenceMap.values()].filter((reference) => !reference.locale || reference.locale === localeConfig?.defaultLocale).map((reference) => reference.navigationId));
|
|
2323
|
+
const routeAliases = [];
|
|
1720
2324
|
const pageFiles = [...files].sort((left, right) => {
|
|
1721
2325
|
const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
1722
2326
|
const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -1735,12 +2339,12 @@ function migrateRepository(options) {
|
|
|
1735
2339
|
skipped++;
|
|
1736
2340
|
continue;
|
|
1737
2341
|
}
|
|
1738
|
-
if (![".md", ".mdx"].includes(
|
|
2342
|
+
if (![".md", ".mdx"].includes(extname4(file.relativePath).toLowerCase())) {
|
|
1739
2343
|
skipped++;
|
|
1740
2344
|
warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
|
|
1741
2345
|
continue;
|
|
1742
2346
|
}
|
|
1743
|
-
const size =
|
|
2347
|
+
const size = lstatSync4(file.absolutePath).size;
|
|
1744
2348
|
if (size > MAX_PAGE_BYTES) {
|
|
1745
2349
|
skipped++;
|
|
1746
2350
|
warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
|
|
@@ -1751,10 +2355,12 @@ function migrateRepository(options) {
|
|
|
1751
2355
|
let locale = referenced?.locale;
|
|
1752
2356
|
let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
|
|
1753
2357
|
if (!referenced && localeConfig) {
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2358
|
+
for (const entry of localeConfig.locales) {
|
|
2359
|
+
const localized = mintlifyLocalizedReference(file.relativePath, entry.code, defaultPageIds);
|
|
2360
|
+
if (localized === file.relativePath) continue;
|
|
2361
|
+
locale = entry.code;
|
|
2362
|
+
navigationId = pageIdFromReference(localized, platform === "mintlify");
|
|
2363
|
+
break;
|
|
1758
2364
|
}
|
|
1759
2365
|
}
|
|
1760
2366
|
if (!navigationId) {
|
|
@@ -1764,8 +2370,8 @@ function migrateRepository(options) {
|
|
|
1764
2370
|
}
|
|
1765
2371
|
const isDefaultLocale = !locale || locale === localeConfig?.defaultLocale;
|
|
1766
2372
|
const id = isDefaultLocale ? navigationId : `${locale}/${navigationId}`;
|
|
1767
|
-
|
|
1768
|
-
|
|
2373
|
+
let raw = inlineMdxSnippets(
|
|
2374
|
+
readFileSync4(file.absolutePath, "utf8"),
|
|
1769
2375
|
file.absolutePath,
|
|
1770
2376
|
repositoryDir,
|
|
1771
2377
|
warnings,
|
|
@@ -1773,13 +2379,14 @@ function migrateRepository(options) {
|
|
|
1773
2379
|
mintlifyProjectRoot ?? docusaurusProjectRoot ?? repositoryDir,
|
|
1774
2380
|
snippetAliases
|
|
1775
2381
|
);
|
|
2382
|
+
if (componentMigrator) raw = componentMigrator.transform(raw, file.absolutePath);
|
|
1776
2383
|
let docusaurusDescriptor;
|
|
1777
2384
|
const page = parseMarkdownPage({
|
|
1778
2385
|
id,
|
|
1779
2386
|
navigationId,
|
|
1780
2387
|
...locale ? { locale } : {},
|
|
1781
2388
|
raw,
|
|
1782
|
-
source: `${options.sourceUrl}#${
|
|
2389
|
+
source: `${options.sourceUrl}#${relative4(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
|
|
1783
2390
|
...platform === "docusaurus" ? {
|
|
1784
2391
|
resolveIdentity: (frontmatter, fallback) => {
|
|
1785
2392
|
const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
|
|
@@ -1816,6 +2423,12 @@ function migrateRepository(options) {
|
|
|
1816
2423
|
}
|
|
1817
2424
|
seenPageIds.add(page.id);
|
|
1818
2425
|
pages.push(page);
|
|
2426
|
+
if (platform === "mintlify") {
|
|
2427
|
+
const sourcePath = exactReferenceKey(file.relativePath);
|
|
2428
|
+
if (sourcePath !== page.id && /^[A-Za-z0-9_./-]+$/.test(sourcePath)) {
|
|
2429
|
+
routeAliases.push({ source: `/${sourcePath}`, destination: `/${page.id}`, permanent: false });
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
1819
2432
|
if (docusaurusDescriptor) {
|
|
1820
2433
|
docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
|
|
1821
2434
|
}
|
|
@@ -1829,7 +2442,7 @@ function migrateRepository(options) {
|
|
|
1829
2442
|
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
1830
2443
|
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
1831
2444
|
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
1832
|
-
if (!
|
|
2445
|
+
if (!existsSync4(root) || !lstatSync4(root).isDirectory()) return [];
|
|
1833
2446
|
return scanFiles(root).map((file) => ({
|
|
1834
2447
|
...file,
|
|
1835
2448
|
relativePath: `${directory}/${file.relativePath}`
|
|
@@ -1838,9 +2451,9 @@ function migrateRepository(options) {
|
|
|
1838
2451
|
let totalAssetBytes = 0;
|
|
1839
2452
|
for (const file of [...files, ...repositoryAssets]) {
|
|
1840
2453
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
1841
|
-
if (!ASSET_EXTENSIONS.has(
|
|
2454
|
+
if (!ASSET_EXTENSIONS.has(extname4(file.relativePath).toLowerCase())) continue;
|
|
1842
2455
|
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
|
|
1843
|
-
const size =
|
|
2456
|
+
const size = lstatSync4(file.absolutePath).size;
|
|
1844
2457
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
1845
2458
|
warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
|
|
1846
2459
|
continue;
|
|
@@ -1848,7 +2461,7 @@ function migrateRepository(options) {
|
|
|
1848
2461
|
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
1849
2462
|
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
1850
2463
|
if (!assetPath) continue;
|
|
1851
|
-
assets.push({ path: assetPath, content:
|
|
2464
|
+
assets.push({ path: assetPath, content: readFileSync4(file.absolutePath) });
|
|
1852
2465
|
totalAssetBytes += size;
|
|
1853
2466
|
}
|
|
1854
2467
|
if (files.length >= MAX_SOURCE_FILES) {
|
|
@@ -1882,11 +2495,16 @@ function migrateRepository(options) {
|
|
|
1882
2495
|
if (openApi) {
|
|
1883
2496
|
const filename = basename(openApi.relativePath);
|
|
1884
2497
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
1885
|
-
assets.push({ path: filename, content:
|
|
2498
|
+
assets.push({ path: filename, content: readFileSync4(openApi.absolutePath) });
|
|
1886
2499
|
}
|
|
1887
2500
|
docsConfig = injectOpenApi(docsConfig, filename);
|
|
1888
2501
|
}
|
|
1889
|
-
if (platform === "mintlify")
|
|
2502
|
+
if (platform === "mintlify") {
|
|
2503
|
+
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source));
|
|
2504
|
+
const aliases = routeAliases.filter((redirect) => !sources.has(redirect.source));
|
|
2505
|
+
if (aliases.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...aliases] };
|
|
2506
|
+
docsConfig = addMintlifyHomepageRedirects(addMintlifyDirectoryRedirects(docsConfig, pages), pages);
|
|
2507
|
+
}
|
|
1890
2508
|
if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
|
|
1891
2509
|
for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
|
|
1892
2510
|
if (plugin.docsDir === configuredDocsDir) continue;
|
|
@@ -1927,6 +2545,7 @@ function migrateRepository(options) {
|
|
|
1927
2545
|
platform,
|
|
1928
2546
|
pages,
|
|
1929
2547
|
assets,
|
|
2548
|
+
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
1930
2549
|
docsConfig,
|
|
1931
2550
|
...mintlifyConfig ? {
|
|
1932
2551
|
site: {
|
|
@@ -2043,6 +2662,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
2043
2662
|
content: renderPage(bundle, page)
|
|
2044
2663
|
})),
|
|
2045
2664
|
...bundle.assets.map((asset) => ({ path: `public/${asset.path}`, content: asset.content })),
|
|
2665
|
+
...(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]),
|
|
2046
2666
|
{ path: "docs.json", content: `${JSON.stringify(config, null, 2)}
|
|
2047
2667
|
` }
|
|
2048
2668
|
];
|
|
@@ -2051,7 +2671,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
2051
2671
|
// src/url.ts
|
|
2052
2672
|
import { load } from "cheerio";
|
|
2053
2673
|
import TurndownService from "turndown";
|
|
2054
|
-
import { parse as
|
|
2674
|
+
import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
|
|
2055
2675
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
2056
2676
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
2057
2677
|
var MAX_DISCOVERED_URLS = 5e3;
|
|
@@ -2613,7 +3233,7 @@ function extractEmbeddedOpenApi(content) {
|
|
|
2613
3233
|
const match = fencePattern.exec(content);
|
|
2614
3234
|
if (!match) return { content };
|
|
2615
3235
|
try {
|
|
2616
|
-
const parsed =
|
|
3236
|
+
const parsed = parseYaml3(match[5], { maxAliasCount: 50 });
|
|
2617
3237
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
|
|
2618
3238
|
const document = parsed;
|
|
2619
3239
|
if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
|