@thallylabs/migrate 0.2.4 → 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 +736 -127
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
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,42 +1035,39 @@ function projectDocusaurusNavigation(input) {
|
|
|
533
1035
|
};
|
|
534
1036
|
}
|
|
535
1037
|
|
|
536
|
-
// src/frontmatter.ts
|
|
537
|
-
import { parse as parseYaml2 } from "yaml";
|
|
538
|
-
function parseFrontmatter(raw) {
|
|
539
|
-
const source = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
540
|
-
const opening = /^---([^\r\n]*)\r?\n/.exec(source);
|
|
541
|
-
if (!opening || opening[1].startsWith("-")) return { content: source, data: {} };
|
|
542
|
-
const language = opening[1].trim().toLowerCase();
|
|
543
|
-
const remainder = source.slice(opening[0].length);
|
|
544
|
-
const closing = /^---[ \t]*\r?$/m.exec(remainder);
|
|
545
|
-
const matter = closing ? remainder.slice(0, closing.index) : remainder;
|
|
546
|
-
let content = closing ? remainder.slice(closing.index + closing[0].length) : "";
|
|
547
|
-
if (content.startsWith("\r\n")) content = content.slice(2);
|
|
548
|
-
else if (content.startsWith("\n")) content = content.slice(1);
|
|
549
|
-
if (matter.trim() === "" || !["", "yaml", "yml"].includes(language)) {
|
|
550
|
-
return { content, data: {} };
|
|
551
|
-
}
|
|
552
|
-
const parsed = parseYaml2(matter);
|
|
553
|
-
return {
|
|
554
|
-
content,
|
|
555
|
-
data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
1038
|
// src/mdx.ts
|
|
1039
|
+
import remarkMdx2 from "remark-mdx";
|
|
1040
|
+
import remarkParse2 from "remark-parse";
|
|
1041
|
+
import { unified as unified2 } from "unified";
|
|
560
1042
|
function titleFromId(id) {
|
|
561
1043
|
const value = id.split("/").at(-1) ?? id;
|
|
562
1044
|
if (value === "introduction") return "Introduction";
|
|
563
1045
|
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
564
1046
|
}
|
|
1047
|
+
var descriptionParser = unified2().use(remarkParse2).use(remarkMdx2);
|
|
565
1048
|
function firstParagraph(content) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (
|
|
569
|
-
|
|
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 "";
|
|
570
1070
|
}
|
|
571
|
-
return "";
|
|
572
1071
|
}
|
|
573
1072
|
function docusaurusAdmonitionTag(kind) {
|
|
574
1073
|
if (kind === "danger") return "Error";
|
|
@@ -646,24 +1145,94 @@ function parseMarkdownPage(input) {
|
|
|
646
1145
|
}
|
|
647
1146
|
|
|
648
1147
|
// src/navigation.ts
|
|
649
|
-
import { existsSync as
|
|
650
|
-
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";
|
|
651
1150
|
var MAX_MINTLIFY_CONFIG_BYTES = 2e6;
|
|
652
1151
|
function addMintlifyDirectoryRedirects(config, pages) {
|
|
653
|
-
const pageIds = new Set(pages.
|
|
1152
|
+
const pageIds = new Set(pages.map((page) => page.id));
|
|
654
1153
|
const redirects = [...config.redirects ?? []];
|
|
655
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
|
+
}
|
|
656
1162
|
for (const page of pages) {
|
|
657
|
-
if (
|
|
658
|
-
const parent = page.
|
|
1163
|
+
if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
|
|
1164
|
+
const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
|
|
659
1165
|
if (!parent || pageIds.has(parent)) continue;
|
|
660
1166
|
const source = `/${parent}`;
|
|
661
|
-
if (
|
|
662
|
-
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 });
|
|
663
1208
|
redirectSources.add(source);
|
|
664
1209
|
}
|
|
665
1210
|
return redirects.length > 0 ? { ...config, redirects } : config;
|
|
666
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
|
+
}
|
|
667
1236
|
var LANGUAGE_LABELS = {
|
|
668
1237
|
ar: "Arabic",
|
|
669
1238
|
de: "German",
|
|
@@ -717,16 +1286,16 @@ function jsonPointer(root, pointer) {
|
|
|
717
1286
|
}, root);
|
|
718
1287
|
}
|
|
719
1288
|
function readMintlifyJson(path, repositoryRoot) {
|
|
720
|
-
if (!
|
|
721
|
-
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)}`);
|
|
722
1291
|
}
|
|
723
1292
|
const realPath = realpathSync(path);
|
|
724
1293
|
const realRoot = realpathSync(repositoryRoot);
|
|
725
|
-
resolveWithin(realRoot,
|
|
726
|
-
if (
|
|
727
|
-
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)}`);
|
|
728
1297
|
}
|
|
729
|
-
return JSON.parse(
|
|
1298
|
+
return JSON.parse(readFileSync3(realPath, "utf8"));
|
|
730
1299
|
}
|
|
731
1300
|
function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
732
1301
|
if (Array.isArray(value)) {
|
|
@@ -736,8 +1305,8 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
736
1305
|
if (!object) return value;
|
|
737
1306
|
if (typeof object.$ref === "string") {
|
|
738
1307
|
const [filePart, pointer = ""] = object.$ref.split("#", 2);
|
|
739
|
-
const referencedFile = filePart ? resolveWithin(
|
|
740
|
-
const relativePath =
|
|
1308
|
+
const referencedFile = filePart ? resolveWithin(dirname2(currentFile), filePart) : currentFile;
|
|
1309
|
+
const relativePath = relative3(repositoryRoot, referencedFile);
|
|
741
1310
|
resolveWithin(repositoryRoot, relativePath);
|
|
742
1311
|
const stackKey = `${referencedFile}#${pointer}`;
|
|
743
1312
|
if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object.$ref}`);
|
|
@@ -762,7 +1331,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
|
|
|
762
1331
|
);
|
|
763
1332
|
}
|
|
764
1333
|
function readMintlifyConfig(repositoryRoot) {
|
|
765
|
-
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);
|
|
766
1335
|
if (!configPath) return null;
|
|
767
1336
|
const raw = readMintlifyJson(configPath, repositoryRoot);
|
|
768
1337
|
return resolveJsonReferences(raw, configPath, repositoryRoot, /* @__PURE__ */ new Set());
|
|
@@ -794,8 +1363,7 @@ function containerPresentation(value) {
|
|
|
794
1363
|
};
|
|
795
1364
|
}
|
|
796
1365
|
function registerReference(value, context) {
|
|
797
|
-
const
|
|
798
|
-
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;
|
|
799
1367
|
const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
|
|
800
1368
|
if (!navigationId) return null;
|
|
801
1369
|
const key = `${context.locale ?? ""}:${value}`;
|
|
@@ -1037,15 +1605,25 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1037
1605
|
const seenReferences = /* @__PURE__ */ new Set();
|
|
1038
1606
|
const warningKeys = /* @__PURE__ */ new Set();
|
|
1039
1607
|
const navigation = objectValue2(config.navigation) ?? config;
|
|
1040
|
-
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
|
+
}) : [];
|
|
1041
1619
|
let tabs = [];
|
|
1042
1620
|
let i18n;
|
|
1043
1621
|
const projectionTrace = {};
|
|
1044
1622
|
if (languages.length > 0) {
|
|
1045
1623
|
const defaultLanguage = languages.find((entry) => entry.default === true) ?? languages[0];
|
|
1046
|
-
const defaultLocale = String(defaultLanguage.language
|
|
1624
|
+
const defaultLocale = String(defaultLanguage.language);
|
|
1047
1625
|
const locales = languages.map((entry) => {
|
|
1048
|
-
const code = String(entry.language
|
|
1626
|
+
const code = String(entry.language);
|
|
1049
1627
|
let label = LANGUAGE_LABELS[code];
|
|
1050
1628
|
try {
|
|
1051
1629
|
label ??= new Intl.DisplayNames(["en"], { type: "language" }).of(code) ?? void 0;
|
|
@@ -1054,10 +1632,12 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1054
1632
|
return { code, label: label ?? code.toUpperCase() };
|
|
1055
1633
|
});
|
|
1056
1634
|
i18n = { defaultLocale, locales };
|
|
1057
|
-
|
|
1058
|
-
|
|
1635
|
+
const defaultPageIds = /* @__PURE__ */ new Set();
|
|
1636
|
+
for (const language of [defaultLanguage, ...languages.filter((entry) => entry !== defaultLanguage)]) {
|
|
1637
|
+
const locale = String(language.language);
|
|
1059
1638
|
const context = {
|
|
1060
1639
|
locale,
|
|
1640
|
+
defaultPageIds,
|
|
1061
1641
|
pathPrefix: options.pathPrefix,
|
|
1062
1642
|
references,
|
|
1063
1643
|
seenReferences,
|
|
@@ -1070,7 +1650,10 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
1070
1650
|
"Documentation",
|
|
1071
1651
|
language === defaultLanguage ? projectionTrace : void 0
|
|
1072
1652
|
);
|
|
1073
|
-
if (language === defaultLanguage)
|
|
1653
|
+
if (language === defaultLanguage) {
|
|
1654
|
+
tabs = languageTabs;
|
|
1655
|
+
for (const reference of references) defaultPageIds.add(reference.navigationId);
|
|
1656
|
+
}
|
|
1074
1657
|
}
|
|
1075
1658
|
} else {
|
|
1076
1659
|
const context = { references, seenReferences, warnings, warningKeys, pathPrefix: options.pathPrefix };
|
|
@@ -1133,8 +1716,8 @@ function topLevelLabel(segment) {
|
|
|
1133
1716
|
function groupsWithinSection(section, pageIds, sectionLabel = topLevelLabel(section)) {
|
|
1134
1717
|
const groups = /* @__PURE__ */ new Map();
|
|
1135
1718
|
for (const id of pageIds) {
|
|
1136
|
-
const
|
|
1137
|
-
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";
|
|
1138
1721
|
const group = groups.get(nestedSegment) ?? [];
|
|
1139
1722
|
group.push(id);
|
|
1140
1723
|
groups.set(nestedSegment, group);
|
|
@@ -1218,7 +1801,7 @@ function buildNavigationFromPages(pages, options = {}) {
|
|
|
1218
1801
|
return { tabs: [{ tab: "Documentation", groups }] };
|
|
1219
1802
|
}
|
|
1220
1803
|
function isDocumentationExtension(filename) {
|
|
1221
|
-
return [".md", ".mdx", ".rst", ".txt"].includes(
|
|
1804
|
+
return [".md", ".mdx", ".rst", ".txt"].includes(extname3(filename).toLowerCase());
|
|
1222
1805
|
}
|
|
1223
1806
|
|
|
1224
1807
|
// src/repository.ts
|
|
@@ -1280,16 +1863,16 @@ var DOCUSAURUS_CONFIG_FILENAMES = [
|
|
|
1280
1863
|
function hasDocusaurusConfig(directory) {
|
|
1281
1864
|
return DOCUSAURUS_CONFIG_FILENAMES.some((filename) => {
|
|
1282
1865
|
const path = resolveWithin(directory, filename);
|
|
1283
|
-
return
|
|
1866
|
+
return existsSync4(path) && lstatSync4(path).isFile();
|
|
1284
1867
|
});
|
|
1285
1868
|
}
|
|
1286
1869
|
function hasMintlifyConfig(directory) {
|
|
1287
1870
|
return ["docs.json", "mint.json"].some((filename) => {
|
|
1288
1871
|
const path = resolveWithin(directory, filename);
|
|
1289
|
-
if (!
|
|
1872
|
+
if (!existsSync4(path) || !lstatSync4(path).isFile()) return false;
|
|
1290
1873
|
if (filename === "mint.json") return true;
|
|
1291
1874
|
try {
|
|
1292
|
-
const config = JSON.parse(
|
|
1875
|
+
const config = JSON.parse(readFileSync4(path, "utf8"));
|
|
1293
1876
|
return String(config.$schema ?? "").includes("mintlify") || "navigation" in config;
|
|
1294
1877
|
} catch {
|
|
1295
1878
|
return false;
|
|
@@ -1303,7 +1886,7 @@ function findMintlifyProjectRoot(repositoryDir, docsDir) {
|
|
|
1303
1886
|
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
1304
1887
|
if (hasMintlifyConfig(path)) return path;
|
|
1305
1888
|
if (!candidate) break;
|
|
1306
|
-
const parent =
|
|
1889
|
+
const parent = dirname3(candidate);
|
|
1307
1890
|
candidate = parent === "." ? "" : parent;
|
|
1308
1891
|
}
|
|
1309
1892
|
}
|
|
@@ -1328,7 +1911,7 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
|
1328
1911
|
const path = resolveWithin(repositoryDir, candidate || ".");
|
|
1329
1912
|
if (hasDocusaurusConfig(path)) return path;
|
|
1330
1913
|
if (!candidate) break;
|
|
1331
|
-
const parent =
|
|
1914
|
+
const parent = dirname3(candidate);
|
|
1332
1915
|
candidate = parent === "." ? "" : parent;
|
|
1333
1916
|
}
|
|
1334
1917
|
}
|
|
@@ -1350,9 +1933,9 @@ function findDocusaurusProjectRoot(repositoryDir, docsDir) {
|
|
|
1350
1933
|
return null;
|
|
1351
1934
|
}
|
|
1352
1935
|
function readDocusaurusConfigSource(projectRoot) {
|
|
1353
|
-
const path = DOCUSAURUS_CONFIG_FILENAMES.map((filename) => resolveWithin(projectRoot, filename)).find((candidate) =>
|
|
1354
|
-
if (!path ||
|
|
1355
|
-
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");
|
|
1356
1939
|
}
|
|
1357
1940
|
function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
1358
1941
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
@@ -1361,7 +1944,7 @@ function primaryDocusaurusDocsDirectory(projectRoot) {
|
|
|
1361
1944
|
}
|
|
1362
1945
|
function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
1363
1946
|
const source = readDocusaurusConfigSource(projectRoot);
|
|
1364
|
-
const projectRelative =
|
|
1947
|
+
const projectRelative = relative4(repositoryDir, projectRoot).replace(/\\/g, "/");
|
|
1365
1948
|
const plugins = [];
|
|
1366
1949
|
const matcher = /['"]@docusaurus\/plugin-content-docs['"][\s\S]{0,3000}?\bpath\s*:\s*(['"])([^'"]+)\1[\s\S]{0,1000}?\brouteBasePath\s*:\s*(['"])([^'"]+)\3/g;
|
|
1367
1950
|
for (const match of source.matchAll(matcher)) {
|
|
@@ -1370,7 +1953,7 @@ function additionalDocusaurusPluginRoots(repositoryDir, projectRoot) {
|
|
|
1370
1953
|
if (!localPath || !routePrefix) continue;
|
|
1371
1954
|
const docsDir = [projectRelative, localPath].filter(Boolean).join("/");
|
|
1372
1955
|
const absolute = resolveWithin(repositoryDir, docsDir);
|
|
1373
|
-
if (!
|
|
1956
|
+
if (!existsSync4(absolute) || !lstatSync4(absolute).isDirectory()) continue;
|
|
1374
1957
|
plugins.push({ docsDir, routePrefix });
|
|
1375
1958
|
}
|
|
1376
1959
|
return plugins;
|
|
@@ -1413,7 +1996,7 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
1413
1996
|
const args = ["clone", "--depth", "1", "--single-branch"];
|
|
1414
1997
|
if (source.branch !== "HEAD") args.push("--branch", source.branch);
|
|
1415
1998
|
args.push("--", source.cloneUrl, targetDir);
|
|
1416
|
-
await new Promise((
|
|
1999
|
+
await new Promise((resolve3, reject) => {
|
|
1417
2000
|
const child = spawn("git", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1418
2001
|
let stderr = "";
|
|
1419
2002
|
child.stderr.setEncoding("utf8");
|
|
@@ -1422,7 +2005,7 @@ async function cloneGitHubRepository(source, targetDir) {
|
|
|
1422
2005
|
});
|
|
1423
2006
|
child.on("error", reject);
|
|
1424
2007
|
child.on("close", (code) => {
|
|
1425
|
-
if (code === 0)
|
|
2008
|
+
if (code === 0) resolve3();
|
|
1426
2009
|
else reject(new Error(`Failed to clone ${source.owner}/${source.repo}: ${stderr.trim() || `git exited ${code}`}`));
|
|
1427
2010
|
});
|
|
1428
2011
|
});
|
|
@@ -1431,19 +2014,19 @@ function detectRepositoryPlatform(repositoryDir, docsDir) {
|
|
|
1431
2014
|
if (findMintlifyProjectRoot(repositoryDir, docsDir)) return "mintlify";
|
|
1432
2015
|
const selectedRoot = docsDir === void 0 ? repositoryDir : resolveWithin(repositoryDir, docsDir || ".");
|
|
1433
2016
|
const docsJson = resolveWithin(selectedRoot, "docs.json");
|
|
1434
|
-
if (
|
|
2017
|
+
if (existsSync4(docsJson)) {
|
|
1435
2018
|
try {
|
|
1436
|
-
const config = JSON.parse(
|
|
2019
|
+
const config = JSON.parse(readFileSync4(docsJson, "utf8"));
|
|
1437
2020
|
if (Array.isArray(config.tabs)) return "thally";
|
|
1438
2021
|
} catch {
|
|
1439
2022
|
}
|
|
1440
2023
|
}
|
|
1441
|
-
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";
|
|
1442
2025
|
if (findDocusaurusProjectRoot(repositoryDir)) return "docusaurus";
|
|
1443
|
-
if (
|
|
1444
|
-
if (
|
|
1445
|
-
if (["astro.config.mjs", "astro.config.ts"].some((name) =>
|
|
1446
|
-
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";
|
|
1447
2030
|
return "unknown";
|
|
1448
2031
|
}
|
|
1449
2032
|
function containsMarkdown(directory, depth = 0) {
|
|
@@ -1451,7 +2034,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
1451
2034
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
1452
2035
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
1453
2036
|
const path = resolveWithin(directory, entry.name);
|
|
1454
|
-
if (entry.isFile() && [".md", ".mdx"].includes(
|
|
2037
|
+
if (entry.isFile() && [".md", ".mdx"].includes(extname4(entry.name).toLowerCase())) return true;
|
|
1455
2038
|
if (entry.isDirectory() && containsMarkdown(path, depth + 1)) return true;
|
|
1456
2039
|
}
|
|
1457
2040
|
return false;
|
|
@@ -1459,7 +2042,7 @@ function containsMarkdown(directory, depth = 0) {
|
|
|
1459
2042
|
function detectRepositoryDocsDir(repositoryDir) {
|
|
1460
2043
|
for (const candidate of ["docs", "documentation", "content", "pages", "src/content", "src/pages", "guide", "guides", ""]) {
|
|
1461
2044
|
const path = resolveWithin(repositoryDir, candidate);
|
|
1462
|
-
if (
|
|
2045
|
+
if (existsSync4(path) && lstatSync4(path).isDirectory() && containsMarkdown(path)) return candidate;
|
|
1463
2046
|
}
|
|
1464
2047
|
return "";
|
|
1465
2048
|
}
|
|
@@ -1472,7 +2055,7 @@ function scanFiles(root) {
|
|
|
1472
2055
|
if (IGNORED_DIRECTORIES.has(entry.name) || entry.isSymbolicLink()) continue;
|
|
1473
2056
|
const path = resolveWithin(directory, entry.name);
|
|
1474
2057
|
if (entry.isDirectory()) visit(path);
|
|
1475
|
-
else if (entry.isFile()) files.push({ absolutePath: path, relativePath:
|
|
2058
|
+
else if (entry.isFile()) files.push({ absolutePath: path, relativePath: relative4(root, path).replace(/\\/g, "/") });
|
|
1476
2059
|
}
|
|
1477
2060
|
}
|
|
1478
2061
|
visit(root);
|
|
@@ -1527,9 +2110,9 @@ function interpolateSnippet(snippet, attributes) {
|
|
|
1527
2110
|
}).join("\n");
|
|
1528
2111
|
}
|
|
1529
2112
|
function resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot) {
|
|
1530
|
-
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, "/");
|
|
1531
2114
|
const candidate = resolveWithin(repositoryRoot, repositoryRelative);
|
|
1532
|
-
resolveWithin(repositoryRoot,
|
|
2115
|
+
resolveWithin(repositoryRoot, relative4(repositoryRoot, candidate));
|
|
1533
2116
|
return candidate;
|
|
1534
2117
|
}
|
|
1535
2118
|
function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
@@ -1543,12 +2126,12 @@ function globalSnippetAliases(files, repositoryRoot, siteRoot) {
|
|
|
1543
2126
|
if (conventionalAlias) aliases.set(conventionalAlias, file.absolutePath);
|
|
1544
2127
|
}
|
|
1545
2128
|
for (const file of files) {
|
|
1546
|
-
if (![".md", ".mdx"].includes(
|
|
1547
|
-
const raw =
|
|
2129
|
+
if (![".md", ".mdx"].includes(extname4(file.relativePath).toLowerCase())) continue;
|
|
2130
|
+
const raw = readFileSync4(file.absolutePath, "utf8");
|
|
1548
2131
|
for (const match of raw.matchAll(matcher)) {
|
|
1549
2132
|
try {
|
|
1550
2133
|
const candidate = resolveSnippetPath(match[2], file.absolutePath, repositoryRoot, siteRoot);
|
|
1551
|
-
if (
|
|
2134
|
+
if (existsSync4(candidate) && lstatSync4(candidate).isFile()) {
|
|
1552
2135
|
aliases.set(match[1], candidate);
|
|
1553
2136
|
}
|
|
1554
2137
|
} catch {
|
|
@@ -1570,12 +2153,12 @@ function repositoryAssetHref(value, currentFile, siteRoot) {
|
|
|
1570
2153
|
} catch {
|
|
1571
2154
|
return null;
|
|
1572
2155
|
}
|
|
1573
|
-
if (!ASSET_EXTENSIONS.has(
|
|
2156
|
+
if (!ASSET_EXTENSIONS.has(extname4(decodedPath).toLowerCase())) return null;
|
|
1574
2157
|
try {
|
|
1575
|
-
const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(
|
|
1576
|
-
const siteRelative =
|
|
2158
|
+
const candidate = decodedPath.startsWith("/") ? resolveWithin(siteRoot, decodedPath.replace(/^\/+/, "")) : resolveWithin(dirname3(currentFile), decodedPath);
|
|
2159
|
+
const siteRelative = relative4(siteRoot, candidate).replace(/\\/g, "/");
|
|
1577
2160
|
resolveWithin(siteRoot, siteRelative);
|
|
1578
|
-
if (!
|
|
2161
|
+
if (!existsSync4(candidate) || !lstatSync4(candidate).isFile()) return null;
|
|
1579
2162
|
const normalized = normalizeAssetPath(siteRelative);
|
|
1580
2163
|
if (!normalized) return null;
|
|
1581
2164
|
const rewritten = `/${normalized}${suffix}`;
|
|
@@ -1601,9 +2184,9 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1601
2184
|
(_statement, componentName, sourcePath) => {
|
|
1602
2185
|
try {
|
|
1603
2186
|
const candidate = resolveSnippetPath(sourcePath, currentFile, repositoryRoot, siteRoot);
|
|
1604
|
-
if (!
|
|
2187
|
+
if (!existsSync4(candidate) || !lstatSync4(candidate).isFile()) throw new Error("file not found");
|
|
1605
2188
|
const nested = inlineMdxSnippets(
|
|
1606
|
-
withoutFrontmatter(
|
|
2189
|
+
withoutFrontmatter(readFileSync4(candidate, "utf8")),
|
|
1607
2190
|
candidate,
|
|
1608
2191
|
repositoryRoot,
|
|
1609
2192
|
warnings,
|
|
@@ -1617,7 +2200,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1617
2200
|
warnings.push({
|
|
1618
2201
|
code: "missing-page",
|
|
1619
2202
|
message: `Imported snippet ${sourcePath} could not be resolved and was left as a comment.`,
|
|
1620
|
-
source:
|
|
2203
|
+
source: relative4(repositoryRoot, currentFile).replace(/\\/g, "/")
|
|
1621
2204
|
});
|
|
1622
2205
|
snippets.set(componentName, `{/* Missing imported snippet: ${sourcePath} */}`);
|
|
1623
2206
|
return "";
|
|
@@ -1627,7 +2210,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
1627
2210
|
for (const [componentName, candidate] of globalAliases) {
|
|
1628
2211
|
if (snippets.has(componentName) || !new RegExp(`<${componentName}(?:\\s|/?>)`).test(withoutImports)) continue;
|
|
1629
2212
|
const nested = inlineMdxSnippets(
|
|
1630
|
-
withoutFrontmatter(
|
|
2213
|
+
withoutFrontmatter(readFileSync4(candidate, "utf8")),
|
|
1631
2214
|
candidate,
|
|
1632
2215
|
repositoryRoot,
|
|
1633
2216
|
warnings,
|
|
@@ -1654,6 +2237,11 @@ function injectOpenApi(config, filename) {
|
|
|
1654
2237
|
else tabs.push({ tab: "API Reference", api: { source: `/${filename}` } });
|
|
1655
2238
|
return { ...config, tabs };
|
|
1656
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
|
+
}
|
|
1657
2245
|
function migrateRepository(options) {
|
|
1658
2246
|
const repositoryDir = options.repositoryDir;
|
|
1659
2247
|
const platform = options.platform ?? detectRepositoryPlatform(repositoryDir, options.docsDir);
|
|
@@ -1663,21 +2251,23 @@ function migrateRepository(options) {
|
|
|
1663
2251
|
if (options.docsDir !== void 0) {
|
|
1664
2252
|
const selected = resolveWithin(repositoryDir, options.docsDir || ".");
|
|
1665
2253
|
if (platform === "docusaurus" && hasDocusaurusConfig(selected)) {
|
|
1666
|
-
const projectRelative =
|
|
2254
|
+
const projectRelative = relative4(repositoryDir, selected).replace(/\\/g, "/");
|
|
1667
2255
|
return [projectRelative, primaryDocusaurusDocsDirectory(selected)].filter(Boolean).join("/");
|
|
1668
2256
|
}
|
|
1669
2257
|
return options.docsDir;
|
|
1670
2258
|
}
|
|
1671
2259
|
if (docusaurusProjectRoot) {
|
|
1672
|
-
const projectRelative =
|
|
2260
|
+
const projectRelative = relative4(repositoryDir, docusaurusProjectRoot).replace(/\\/g, "/");
|
|
1673
2261
|
return [projectRelative, primaryDocusaurusDocsDirectory(docusaurusProjectRoot)].filter(Boolean).join("/");
|
|
1674
2262
|
}
|
|
1675
2263
|
if (platform === "mintlify" && mintlifyProjectRoot) {
|
|
1676
|
-
return
|
|
2264
|
+
return relative4(repositoryDir, mintlifyProjectRoot).replace(/\\/g, "/");
|
|
1677
2265
|
}
|
|
1678
2266
|
return platform === "mintlify" ? "" : detectRepositoryDocsDir(repositoryDir);
|
|
1679
2267
|
})();
|
|
1680
2268
|
const warnings = [];
|
|
2269
|
+
const componentRoot = mintlifyProjectRoot ?? repositoryDir;
|
|
2270
|
+
const componentMigrator = platform === "mintlify" ? createComponentMigrator(componentRoot, warnings, componentSourceIdentity(options.sourceUrl, repositoryDir, componentRoot)) : void 0;
|
|
1681
2271
|
let docsConfig = { tabs: [] };
|
|
1682
2272
|
const referenceMap = /* @__PURE__ */ new Map();
|
|
1683
2273
|
const exactReferenceMap = /* @__PURE__ */ new Map();
|
|
@@ -1694,9 +2284,10 @@ function migrateRepository(options) {
|
|
|
1694
2284
|
warnings.push(...projected.warnings);
|
|
1695
2285
|
for (const [index, reference] of projected.pageReferences.entries()) {
|
|
1696
2286
|
const key = normalizedReferenceKey(reference.ref);
|
|
1697
|
-
referenceMap.set(key, reference);
|
|
1698
|
-
|
|
1699
|
-
|
|
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);
|
|
1700
2291
|
}
|
|
1701
2292
|
}
|
|
1702
2293
|
} catch (error) {
|
|
@@ -1717,7 +2308,7 @@ function migrateRepository(options) {
|
|
|
1717
2308
|
}
|
|
1718
2309
|
}
|
|
1719
2310
|
const contentRoot = resolveWithin(repositoryDir, configuredDocsDir);
|
|
1720
|
-
if (!
|
|
2311
|
+
if (!existsSync4(contentRoot) || !lstatSync4(contentRoot).isDirectory()) {
|
|
1721
2312
|
throw new Error(`Documentation directory does not exist: ${configuredDocsDir || "."}`);
|
|
1722
2313
|
}
|
|
1723
2314
|
const files = scanFiles(contentRoot);
|
|
@@ -1728,6 +2319,8 @@ function migrateRepository(options) {
|
|
|
1728
2319
|
let skipped = 0;
|
|
1729
2320
|
let discovered = files.length;
|
|
1730
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 = [];
|
|
1731
2324
|
const pageFiles = [...files].sort((left, right) => {
|
|
1732
2325
|
const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
1733
2326
|
const rightOrder = referenceOrder.get(normalizedReferenceKey(right.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -1746,12 +2339,12 @@ function migrateRepository(options) {
|
|
|
1746
2339
|
skipped++;
|
|
1747
2340
|
continue;
|
|
1748
2341
|
}
|
|
1749
|
-
if (![".md", ".mdx"].includes(
|
|
2342
|
+
if (![".md", ".mdx"].includes(extname4(file.relativePath).toLowerCase())) {
|
|
1750
2343
|
skipped++;
|
|
1751
2344
|
warnings.push({ code: "skipped-file", message: "Only Markdown and MDX are imported without an explicit converter.", source: file.relativePath });
|
|
1752
2345
|
continue;
|
|
1753
2346
|
}
|
|
1754
|
-
const size =
|
|
2347
|
+
const size = lstatSync4(file.absolutePath).size;
|
|
1755
2348
|
if (size > MAX_PAGE_BYTES) {
|
|
1756
2349
|
skipped++;
|
|
1757
2350
|
warnings.push({ code: "skipped-file", message: "Page exceeded the 2 MB repository import limit.", source: file.relativePath });
|
|
@@ -1762,10 +2355,12 @@ function migrateRepository(options) {
|
|
|
1762
2355
|
let locale = referenced?.locale;
|
|
1763
2356
|
let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
|
|
1764
2357
|
if (!referenced && localeConfig) {
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
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;
|
|
1769
2364
|
}
|
|
1770
2365
|
}
|
|
1771
2366
|
if (!navigationId) {
|
|
@@ -1775,8 +2370,8 @@ function migrateRepository(options) {
|
|
|
1775
2370
|
}
|
|
1776
2371
|
const isDefaultLocale = !locale || locale === localeConfig?.defaultLocale;
|
|
1777
2372
|
const id = isDefaultLocale ? navigationId : `${locale}/${navigationId}`;
|
|
1778
|
-
|
|
1779
|
-
|
|
2373
|
+
let raw = inlineMdxSnippets(
|
|
2374
|
+
readFileSync4(file.absolutePath, "utf8"),
|
|
1780
2375
|
file.absolutePath,
|
|
1781
2376
|
repositoryDir,
|
|
1782
2377
|
warnings,
|
|
@@ -1784,13 +2379,14 @@ function migrateRepository(options) {
|
|
|
1784
2379
|
mintlifyProjectRoot ?? docusaurusProjectRoot ?? repositoryDir,
|
|
1785
2380
|
snippetAliases
|
|
1786
2381
|
);
|
|
2382
|
+
if (componentMigrator) raw = componentMigrator.transform(raw, file.absolutePath);
|
|
1787
2383
|
let docusaurusDescriptor;
|
|
1788
2384
|
const page = parseMarkdownPage({
|
|
1789
2385
|
id,
|
|
1790
2386
|
navigationId,
|
|
1791
2387
|
...locale ? { locale } : {},
|
|
1792
2388
|
raw,
|
|
1793
|
-
source: `${options.sourceUrl}#${
|
|
2389
|
+
source: `${options.sourceUrl}#${relative4(repositoryDir, file.absolutePath).replace(/\\/g, "/")}`,
|
|
1794
2390
|
...platform === "docusaurus" ? {
|
|
1795
2391
|
resolveIdentity: (frontmatter, fallback) => {
|
|
1796
2392
|
const resolved = resolveDocusaurusPageIdentity(file.relativePath, frontmatter, fallback);
|
|
@@ -1827,6 +2423,12 @@ function migrateRepository(options) {
|
|
|
1827
2423
|
}
|
|
1828
2424
|
seenPageIds.add(page.id);
|
|
1829
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
|
+
}
|
|
1830
2432
|
if (docusaurusDescriptor) {
|
|
1831
2433
|
docusaurusDescriptors.push({ ...docusaurusDescriptor, title: page.title });
|
|
1832
2434
|
}
|
|
@@ -1840,7 +2442,7 @@ function migrateRepository(options) {
|
|
|
1840
2442
|
const docusaurusAssetRoot = docusaurusProjectRoot ?? repositoryDir;
|
|
1841
2443
|
const repositoryAssets = platform === "docusaurus" && configuredDocsDir && !options.docusaurusSkipAssets ? ["static", "public"].flatMap((directory) => {
|
|
1842
2444
|
const root = resolveWithin(docusaurusAssetRoot, directory);
|
|
1843
|
-
if (!
|
|
2445
|
+
if (!existsSync4(root) || !lstatSync4(root).isDirectory()) return [];
|
|
1844
2446
|
return scanFiles(root).map((file) => ({
|
|
1845
2447
|
...file,
|
|
1846
2448
|
relativePath: `${directory}/${file.relativePath}`
|
|
@@ -1849,9 +2451,9 @@ function migrateRepository(options) {
|
|
|
1849
2451
|
let totalAssetBytes = 0;
|
|
1850
2452
|
for (const file of [...files, ...repositoryAssets]) {
|
|
1851
2453
|
const firstSegment = file.relativePath.split("/", 1)[0].toLowerCase();
|
|
1852
|
-
if (!ASSET_EXTENSIONS.has(
|
|
2454
|
+
if (!ASSET_EXTENSIONS.has(extname4(file.relativePath).toLowerCase())) continue;
|
|
1853
2455
|
if (platform !== "mintlify" && !ASSET_DIRECTORIES.has(firstSegment)) continue;
|
|
1854
|
-
const size =
|
|
2456
|
+
const size = lstatSync4(file.absolutePath).size;
|
|
1855
2457
|
if (size > MAX_ASSET_BYTES || totalAssetBytes + size > MAX_TOTAL_ASSET_BYTES) {
|
|
1856
2458
|
warnings.push({ code: "limit-reached", message: "An asset was skipped because the migration asset budget was exhausted.", source: file.relativePath });
|
|
1857
2459
|
continue;
|
|
@@ -1859,7 +2461,7 @@ function migrateRepository(options) {
|
|
|
1859
2461
|
const isDocusaurusStatic = platform === "docusaurus" && firstSegment === "static";
|
|
1860
2462
|
const assetPath = normalizeAssetPath(firstSegment === "public" ? file.relativePath.slice("public/".length) : isDocusaurusStatic ? file.relativePath.slice("static/".length) : file.relativePath);
|
|
1861
2463
|
if (!assetPath) continue;
|
|
1862
|
-
assets.push({ path: assetPath, content:
|
|
2464
|
+
assets.push({ path: assetPath, content: readFileSync4(file.absolutePath) });
|
|
1863
2465
|
totalAssetBytes += size;
|
|
1864
2466
|
}
|
|
1865
2467
|
if (files.length >= MAX_SOURCE_FILES) {
|
|
@@ -1893,11 +2495,16 @@ function migrateRepository(options) {
|
|
|
1893
2495
|
if (openApi) {
|
|
1894
2496
|
const filename = basename(openApi.relativePath);
|
|
1895
2497
|
if (!assets.some((asset) => asset.path === filename)) {
|
|
1896
|
-
assets.push({ path: filename, content:
|
|
2498
|
+
assets.push({ path: filename, content: readFileSync4(openApi.absolutePath) });
|
|
1897
2499
|
}
|
|
1898
2500
|
docsConfig = injectOpenApi(docsConfig, filename);
|
|
1899
2501
|
}
|
|
1900
|
-
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
|
+
}
|
|
1901
2508
|
if (platform === "docusaurus" && docusaurusProjectRoot && !options.docusaurusSkipPlugins) {
|
|
1902
2509
|
for (const plugin of additionalDocusaurusPluginRoots(repositoryDir, docusaurusProjectRoot)) {
|
|
1903
2510
|
if (plugin.docsDir === configuredDocsDir) continue;
|
|
@@ -1938,6 +2545,7 @@ function migrateRepository(options) {
|
|
|
1938
2545
|
platform,
|
|
1939
2546
|
pages,
|
|
1940
2547
|
assets,
|
|
2548
|
+
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
1941
2549
|
docsConfig,
|
|
1942
2550
|
...mintlifyConfig ? {
|
|
1943
2551
|
site: {
|
|
@@ -2054,6 +2662,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
2054
2662
|
content: renderPage(bundle, page)
|
|
2055
2663
|
})),
|
|
2056
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]),
|
|
2057
2666
|
{ path: "docs.json", content: `${JSON.stringify(config, null, 2)}
|
|
2058
2667
|
` }
|
|
2059
2668
|
];
|