@thallylabs/migrate 0.2.19 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +84 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -263,7 +263,8 @@ function unwrapDocusaurusMdxCodeBlocks(body) {
|
|
|
263
263
|
}
|
|
264
264
|
return isMdxCodeBlock ? body : output.join("\n");
|
|
265
265
|
}
|
|
266
|
-
|
|
266
|
+
var HTML_ID = /^[^\s"'<>`{}&]+$/u;
|
|
267
|
+
function normalizeExplicitHeadingIds(raw, warn) {
|
|
267
268
|
const { front, body } = splitFrontmatterBlock(raw);
|
|
268
269
|
return front + replaceOutsideCode(body, (segment) => segment.split("\n").map((line) => {
|
|
269
270
|
if (!/^[ \t]{0,3}#{1,6}[ \t]+/.test(line)) return line;
|
|
@@ -272,10 +273,12 @@ function normalizeExplicitHeadingIds(raw) {
|
|
|
272
273
|
const marker = trimmed.lastIndexOf(" {#");
|
|
273
274
|
if (marker >= 0) {
|
|
274
275
|
const id = trimmed.slice(marker + 3, -1);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
const heading = trimmed.slice(0, marker).trimEnd();
|
|
277
|
+
if (/[{}]/.test(id)) return line;
|
|
278
|
+
if (HTML_ID.test(id)) return `<a id="${id}"></a>
|
|
279
|
+
${heading}`;
|
|
280
|
+
warn?.(`Heading anchor {#${id}} is not a valid HTML id and was removed from "${heading.replace(/^\s*#+\s*/, "")}".`);
|
|
281
|
+
return heading;
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
if (trimmed.endsWith("*/}")) {
|
|
@@ -283,7 +286,7 @@ ${trimmed.slice(0, marker).trimEnd()}`;
|
|
|
283
286
|
if (marker >= 0) {
|
|
284
287
|
const comment = trimmed.slice(marker + 4, -3).trim();
|
|
285
288
|
const id = comment.startsWith("#") ? comment.slice(1) : "";
|
|
286
|
-
if (
|
|
289
|
+
if (HTML_ID.test(id)) {
|
|
287
290
|
return `<a id="${id}"></a>
|
|
288
291
|
${trimmed.slice(0, marker).trimEnd()}`;
|
|
289
292
|
}
|
|
@@ -671,13 +674,25 @@ function maskCode(body) {
|
|
|
671
674
|
if (fenceBuffer.length) lines.push(stash(fenceBuffer.join("\n")));
|
|
672
675
|
return {
|
|
673
676
|
masked: lines.join("\n"),
|
|
674
|
-
unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
|
|
677
|
+
unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)]),
|
|
678
|
+
openFence: codeFence
|
|
675
679
|
};
|
|
676
680
|
}
|
|
681
|
+
function closeOpenFence(body) {
|
|
682
|
+
const { openFence } = maskCode(body);
|
|
683
|
+
return openFence ? `${body}${body.endsWith("\n") ? "" : "\n"}${openFence}
|
|
684
|
+
` : body;
|
|
685
|
+
}
|
|
677
686
|
function replaceOutsideCode(body, transform) {
|
|
678
687
|
const { masked, unmask } = maskCode(body);
|
|
679
688
|
return unmask(transform(masked));
|
|
680
689
|
}
|
|
690
|
+
function mdxComment(text2) {
|
|
691
|
+
return `{/*${text2.replace(/\*\//g, "* /")}*/}`;
|
|
692
|
+
}
|
|
693
|
+
function replaceOutsideCodeAndComments(body, transform) {
|
|
694
|
+
return replaceOutsideCode(body, (masked) => masked.split(/(\{\s*\/\*[\s\S]*?\*\/\s*\})/).map((part, index) => index % 2 ? part : transform(part)).join(""));
|
|
695
|
+
}
|
|
681
696
|
function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePath) {
|
|
682
697
|
const rewrite = (target) => {
|
|
683
698
|
if (!target || target.startsWith("/") || target.startsWith("#") || target.startsWith("?") || target.includes("\\") || /[\u0000-\u001f\u007f]/.test(target) || /^[A-Za-z][A-Za-z\d+.-]*:/.test(target)) return target;
|
|
@@ -739,7 +754,7 @@ function normalizeFernFileTrees(body) {
|
|
|
739
754
|
));
|
|
740
755
|
}
|
|
741
756
|
function normalizeHtmlComments(body) {
|
|
742
|
-
return replaceOutsideCode(body, (whole) => whole.replace(/<!--([\s\S]*?)-->/g, (_match, comment) =>
|
|
757
|
+
return replaceOutsideCode(body, (whole) => whole.replace(/<!--([\s\S]*?)-->/g, (_match, comment) => mdxComment(comment)));
|
|
743
758
|
}
|
|
744
759
|
function normalizeFernCallouts(body) {
|
|
745
760
|
return replaceOutsideCode(body, (whole) => {
|
|
@@ -1230,7 +1245,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
|
|
|
1230
1245
|
}
|
|
1231
1246
|
if (runMintlify) rewritten = normalizeMintlifyParameterAnchors(rewritten);
|
|
1232
1247
|
rewritten = replaceOutsideCode(rewritten, (segment) => {
|
|
1233
|
-
let result = convertHtmlStyleAttributes(segment).replace(/<!--([\s\S]*?)-->/g, (_match, content) =>
|
|
1248
|
+
let result = convertHtmlStyleAttributes(segment).replace(/<!--([\s\S]*?)-->/g, (_match, content) => mdxComment(content)).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Warn(\s[^>]*)?>/g, "<Warning$1>").replace(/<\/Warn>/g, "</Warning>").replace(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
|
|
1234
1249
|
if (runDocusaurus) {
|
|
1235
1250
|
result = result.replace(/<TabItem\b([^>]*)>/g, (_match, attributes) => {
|
|
1236
1251
|
const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
|
|
@@ -2380,7 +2395,8 @@ function neutralizeUnresolvableImportsWithoutAst(content, currentFile, warn) {
|
|
|
2380
2395
|
});
|
|
2381
2396
|
for (const { name, specifier } of dropped) {
|
|
2382
2397
|
warn(`Component import ${JSON.stringify(specifier)} could not be resolved because this page has invalid MDX (see the previous warning), so the import and its usage were removed to keep the site building.`, currentFile);
|
|
2383
|
-
|
|
2398
|
+
const removed = mdxComment(` Removed <${name}>: unresolved during fallback migration `);
|
|
2399
|
+
result = replaceOutsideCodeAndComments(result, (segment) => segment.replace(new RegExp(`<${name}(?:\\s[^>]*)?/>`, "g"), removed).replace(new RegExp(`<${name}(?:\\s[^>]*)?>[\\s\\S]*?<\\/${name}>`, "g"), removed));
|
|
2384
2400
|
}
|
|
2385
2401
|
return result;
|
|
2386
2402
|
}
|
|
@@ -3043,7 +3059,7 @@ ${inlineSource}` });
|
|
|
3043
3059
|
const start = node.position.start.offset;
|
|
3044
3060
|
const end = node.position.end.offset;
|
|
3045
3061
|
const videoId = youtubeEmbedVideoId(node, specifier);
|
|
3046
|
-
const value = videoId ? `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" title="Embedded video" allowFullScreen />` : `
|
|
3062
|
+
const value = videoId ? `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" title="Embedded video" allowFullScreen />` : mdxComment(` Removed <${node.name}>: unsupported import '${specifier}' `);
|
|
3047
3063
|
edits.push({ start, end, value });
|
|
3048
3064
|
});
|
|
3049
3065
|
const rendered = applyReplacements(content, edits);
|
|
@@ -6641,7 +6657,7 @@ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, la
|
|
|
6641
6657
|
const dropped = ranked.slice(budget).map(({ file }) => file);
|
|
6642
6658
|
if (warnings) {
|
|
6643
6659
|
const droppedPages = label === "file" ? dropped.filter((file) => isDocumentationExtension(file.relativePath)) : dropped;
|
|
6644
|
-
const noun = label === "file" ? "page" : "image or media file";
|
|
6660
|
+
const noun = label === "file" ? "page" : label === "snippet" ? "snippet file" : "image or media file";
|
|
6645
6661
|
const droppedVersions = /* @__PURE__ */ new Set();
|
|
6646
6662
|
for (const file of dropped) {
|
|
6647
6663
|
const firstSegment = file.relativePath.split("/", 1)[0];
|
|
@@ -6651,16 +6667,19 @@ function selectGroupWithinBudget(scanned, rank, warnings, allVersionPrefixes, la
|
|
|
6651
6667
|
const rest = droppedPages.length - examples.length;
|
|
6652
6668
|
warnings.push({
|
|
6653
6669
|
code: "limit-reached",
|
|
6654
|
-
message: `This repository has more than ${budget} ${label === "file" ? "files" : "assets"}, so only the first ${budget} (in navigation order, default version first) were migrated. ${droppedPages.length} ${noun}(s) were left out` + (examples.length > 0 ? `: ${examples.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}` : "") + (droppedVersions.size > 0 ? ` (versions: ${[...droppedVersions].slice(0, 5).join(", ")}${droppedVersions.size > 5 ? `, and ${droppedVersions.size - 5} more` : ""})` : "") + (label === "
|
|
6670
|
+
message: `This repository has more than ${budget} ${label === "file" ? "files" : label === "snippet" ? "snippets" : "assets"}, so only the first ${budget} (in navigation order, default version first) were migrated. ${droppedPages.length} ${noun}(s) were left out` + (examples.length > 0 ? `: ${examples.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}` : "") + (droppedVersions.size > 0 ? ` (versions: ${[...droppedVersions].slice(0, 5).join(", ")}${droppedVersions.size > 5 ? `, and ${droppedVersions.size - 5} more` : ""})` : "") + (label === "asset" ? ". Copy them into public/ manually if your pages use them." : ". To include them, run the migration on a smaller part of the repository with --docs-dir.")
|
|
6655
6671
|
});
|
|
6656
6672
|
}
|
|
6657
6673
|
return ranked.slice(0, budget).map(({ file }) => file);
|
|
6658
6674
|
}
|
|
6659
6675
|
function selectFilesWithinBudget(scanned, rank, warnings, allVersionPrefixes, budget = MAX_SOURCE_FILES) {
|
|
6660
6676
|
const isAsset = (file) => ASSET_EXTENSIONS.has(extname6(file.relativePath).toLowerCase());
|
|
6677
|
+
const isSnippet = (file) => file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
|
|
6678
|
+
const snippetFiles = scanned.filter((file) => !isAsset(file) && isSnippet(file));
|
|
6661
6679
|
const assetFiles = scanned.filter(isAsset);
|
|
6662
|
-
const otherFiles = scanned.filter((file) => !isAsset(file));
|
|
6680
|
+
const otherFiles = scanned.filter((file) => !isAsset(file) && !isSnippet(file));
|
|
6663
6681
|
return [
|
|
6682
|
+
...selectGroupWithinBudget(snippetFiles, rank, warnings, allVersionPrefixes, "snippet", budget),
|
|
6664
6683
|
...selectGroupWithinBudget(otherFiles, rank, warnings, allVersionPrefixes, "file", budget),
|
|
6665
6684
|
...selectGroupWithinBudget(assetFiles, rank, warnings, allVersionPrefixes, "asset", budget)
|
|
6666
6685
|
];
|
|
@@ -7159,6 +7178,13 @@ function interpolateSnippet(snippet, attributes, children) {
|
|
|
7159
7178
|
});
|
|
7160
7179
|
}).join("\n");
|
|
7161
7180
|
}
|
|
7181
|
+
function isolateFences(snippet, source, start, end) {
|
|
7182
|
+
const fence = /^\s*(?:`{3,}|~{3,})/;
|
|
7183
|
+
const lines = snippet.split("\n");
|
|
7184
|
+
const before = source.slice(source.lastIndexOf("\n", start - 1) + 1, start);
|
|
7185
|
+
const after = source.slice(end, source.indexOf("\n", end) < 0 ? void 0 : source.indexOf("\n", end));
|
|
7186
|
+
return `${fence.test(lines[0]) && /\S/.test(before) ? "\n" : ""}${snippet}${fence.test(lines[lines.length - 1]) && /\S/.test(after) ? "\n" : ""}`;
|
|
7187
|
+
}
|
|
7162
7188
|
function snippetComponentBody(source, componentName) {
|
|
7163
7189
|
const parsed = ts3.createSourceFile("snippet.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
7164
7190
|
for (const statement of parsed.statements) {
|
|
@@ -7607,7 +7633,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
7607
7633
|
message: `Imported snippet ${sourcePath} could not be resolved and was left as a comment.`,
|
|
7608
7634
|
source: relative5(repositoryRoot, currentFile).replace(/\\/g, "/")
|
|
7609
7635
|
});
|
|
7610
|
-
snippets.set(componentName, `
|
|
7636
|
+
snippets.set(componentName, mdxComment(` Missing imported snippet: ${sourcePath} `));
|
|
7611
7637
|
return "";
|
|
7612
7638
|
}
|
|
7613
7639
|
}
|
|
@@ -7676,48 +7702,49 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
7676
7702
|
if (declaration) preservedDeclarations.set(componentName, declaration);
|
|
7677
7703
|
else snippets.set(componentName, snippetComponentBody(nested, componentName));
|
|
7678
7704
|
}
|
|
7679
|
-
let result = withoutImports
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
});
|
|
7686
|
-
}
|
|
7687
|
-
result = result.replace(SNIPPET_TAG_PATTERN, (_tag, doubleQuoted, singleQuoted) => {
|
|
7688
|
-
const filePath = doubleQuoted ?? singleQuoted;
|
|
7689
|
-
try {
|
|
7690
|
-
const candidate = [
|
|
7691
|
-
() => resolveWithin(siteRoot, `snippets/${filePath}`),
|
|
7692
|
-
() => resolveSnippetPath(filePath, currentFile, repositoryRoot, siteRoot)
|
|
7693
|
-
].map((resolveCandidate) => {
|
|
7694
|
-
try {
|
|
7695
|
-
return resolveCandidate();
|
|
7696
|
-
} catch {
|
|
7697
|
-
return void 0;
|
|
7698
|
-
}
|
|
7699
|
-
}).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
|
|
7700
|
-
if (!candidate) throw new Error("file not found");
|
|
7701
|
-
const blocked = blockInline(candidate, filePath, `Snippet file="${filePath}"`);
|
|
7702
|
-
if (blocked) return blocked;
|
|
7703
|
-
return inlineMdxSnippets(
|
|
7704
|
-
withoutFrontmatter(readFileSync5(candidate, "utf8")),
|
|
7705
|
-
candidate,
|
|
7706
|
-
repositoryRoot,
|
|
7707
|
-
warnings,
|
|
7708
|
-
depth + 1,
|
|
7709
|
-
siteRoot,
|
|
7710
|
-
globalAliases,
|
|
7711
|
-
gate
|
|
7712
|
-
);
|
|
7713
|
-
} catch {
|
|
7714
|
-
warnings.push({
|
|
7715
|
-
code: "missing-page",
|
|
7716
|
-
message: `Snippet file="${filePath}" could not be resolved and was left as a comment.`,
|
|
7717
|
-
source: relative5(repositoryRoot, currentFile).replace(/\\/g, "/")
|
|
7705
|
+
let result = replaceOutsideCodeAndComments(withoutImports, (segment) => {
|
|
7706
|
+
for (const [componentName, snippet] of snippets) {
|
|
7707
|
+
segment = segment.replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)\\s*/>`, "g"), (tag, attributes, offset, source2) => {
|
|
7708
|
+
return isolateFences(interpolateSnippet(snippet, attributes), source2, offset, offset + tag.length);
|
|
7709
|
+
}).replace(new RegExp(`<${componentName}((?:\\s[^>]*)?)>([\\s\\S]*?)<\\/${componentName}>`, "g"), (tag, attributes, children, offset, source2) => {
|
|
7710
|
+
return isolateFences(interpolateSnippet(snippet, attributes, children), source2, offset, offset + tag.length);
|
|
7718
7711
|
});
|
|
7719
|
-
return `{/* Missing snippet: ${filePath} */}`;
|
|
7720
7712
|
}
|
|
7713
|
+
return segment.replace(SNIPPET_TAG_PATTERN, (tag, doubleQuoted, singleQuoted, offset, source2) => {
|
|
7714
|
+
const filePath = doubleQuoted ?? singleQuoted;
|
|
7715
|
+
try {
|
|
7716
|
+
const candidate = [
|
|
7717
|
+
() => resolveWithin(siteRoot, `snippets/${filePath}`),
|
|
7718
|
+
() => resolveSnippetPath(filePath, currentFile, repositoryRoot, siteRoot)
|
|
7719
|
+
].map((resolveCandidate) => {
|
|
7720
|
+
try {
|
|
7721
|
+
return resolveCandidate();
|
|
7722
|
+
} catch {
|
|
7723
|
+
return void 0;
|
|
7724
|
+
}
|
|
7725
|
+
}).find((path) => path !== void 0 && existsSync5(path) && lstatSync5(path).isFile());
|
|
7726
|
+
if (!candidate) throw new Error("file not found");
|
|
7727
|
+
const blocked = blockInline(candidate, filePath, `Snippet file="${filePath}"`);
|
|
7728
|
+
if (blocked) return blocked;
|
|
7729
|
+
return isolateFences(inlineMdxSnippets(
|
|
7730
|
+
withoutFrontmatter(readFileSync5(candidate, "utf8")),
|
|
7731
|
+
candidate,
|
|
7732
|
+
repositoryRoot,
|
|
7733
|
+
warnings,
|
|
7734
|
+
depth + 1,
|
|
7735
|
+
siteRoot,
|
|
7736
|
+
globalAliases,
|
|
7737
|
+
gate
|
|
7738
|
+
), source2, offset, offset + tag.length);
|
|
7739
|
+
} catch {
|
|
7740
|
+
warnings.push({
|
|
7741
|
+
code: "missing-page",
|
|
7742
|
+
message: `Snippet file="${filePath}" could not be resolved and was left as a comment.`,
|
|
7743
|
+
source: relative5(repositoryRoot, currentFile).replace(/\\/g, "/")
|
|
7744
|
+
});
|
|
7745
|
+
return mdxComment(` Missing snippet: ${filePath} `);
|
|
7746
|
+
}
|
|
7747
|
+
});
|
|
7721
7748
|
});
|
|
7722
7749
|
if (preservedDeclarations.size > 0) {
|
|
7723
7750
|
const frontmatter = result.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
@@ -7726,7 +7753,7 @@ function inlineMdxSnippets(raw, currentFile, repositoryRoot, warnings, depth = 0
|
|
|
7726
7753
|
|
|
7727
7754
|
${result.slice(prefix.length)}`;
|
|
7728
7755
|
}
|
|
7729
|
-
return depth === 0 ? hoistMdxImports(result) : result;
|
|
7756
|
+
return depth === 0 ? hoistMdxImports(result) : closeOpenFence(result);
|
|
7730
7757
|
}
|
|
7731
7758
|
function injectOpenApiSpecs(config, specs, warnings) {
|
|
7732
7759
|
const tabs = config.tabs.map((tab) => ({ ...tab }));
|
|
@@ -8214,7 +8241,7 @@ function migrateRepository(options) {
|
|
|
8214
8241
|
const publishedText = trackPublishedRefs ? raw : void 0;
|
|
8215
8242
|
const pageAssetReferences = [];
|
|
8216
8243
|
if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
|
|
8217
|
-
raw = normalizeExplicitHeadingIds(raw);
|
|
8244
|
+
raw = normalizeExplicitHeadingIds(raw, (message) => warnings.push({ code: "invalid-page", message, source: file.relativePath }));
|
|
8218
8245
|
}
|
|
8219
8246
|
if (componentMigrator) {
|
|
8220
8247
|
const warningsBeforeTransform = warnings.length;
|