@thallylabs/migrate 0.2.42 → 0.2.43
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 +8 -0
- package/dist/index.d.ts +34 -1
- package/dist/index.js +549 -204
- package/dist/index.js.map +1 -1
- package/dist/url-migration.js +226 -41
- package/dist/url-migration.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/repository.ts
|
|
2
2
|
import { compileSync as compileSync2 } from "@mdx-js/mdx";
|
|
3
|
-
import { createHash as
|
|
3
|
+
import { createHash as createHash5 } from "crypto";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import {
|
|
6
6
|
closeSync,
|
|
@@ -801,9 +801,9 @@ import { basename, dirname, extname as extname2, posix as posix2, relative as re
|
|
|
801
801
|
import postcss from "postcss";
|
|
802
802
|
import selectorParser from "postcss-selector-parser";
|
|
803
803
|
import remarkMdx2 from "remark-mdx";
|
|
804
|
-
import
|
|
804
|
+
import remarkParse3 from "remark-parse";
|
|
805
805
|
import ts2 from "typescript";
|
|
806
|
-
import { unified as
|
|
806
|
+
import { unified as unified3 } from "unified";
|
|
807
807
|
|
|
808
808
|
// src/frontmatter.ts
|
|
809
809
|
import { parse as parseYaml } from "yaml";
|
|
@@ -856,8 +856,8 @@ import { compileSync } from "@mdx-js/mdx";
|
|
|
856
856
|
import { nameToEmoji } from "gemoji";
|
|
857
857
|
import remarkGfm from "remark-gfm";
|
|
858
858
|
import remarkMdx from "remark-mdx";
|
|
859
|
-
import
|
|
860
|
-
import { unified } from "unified";
|
|
859
|
+
import remarkParse2 from "remark-parse";
|
|
860
|
+
import { unified as unified2 } from "unified";
|
|
861
861
|
|
|
862
862
|
// src/mintlify-extras.ts
|
|
863
863
|
import { extname } from "path";
|
|
@@ -885,8 +885,8 @@ function slugifySegment(value, preserveCase = false) {
|
|
|
885
885
|
} catch {
|
|
886
886
|
}
|
|
887
887
|
const normalized = preserveCase ? decoded : decoded.toLowerCase();
|
|
888
|
-
const
|
|
889
|
-
return
|
|
888
|
+
const slug3 = normalized.replace(/\.(?:html?|mdx?)$/i, "").replace(SAFE_SEGMENT, "-").replace(/(^-|-$)/g, "");
|
|
889
|
+
return slug3 === "." || slug3 === ".." ? "" : slug3;
|
|
890
890
|
}
|
|
891
891
|
function pageIdFromReference(value, preserveCase = false) {
|
|
892
892
|
const withoutQuery = value.split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -1125,13 +1125,103 @@ function playgroundDisplay(value, where, warn) {
|
|
|
1125
1125
|
return void 0;
|
|
1126
1126
|
}
|
|
1127
1127
|
|
|
1128
|
+
// src/fence-meta.ts
|
|
1129
|
+
import remarkParse from "remark-parse";
|
|
1130
|
+
import { unified } from "unified";
|
|
1131
|
+
function delimitedEnd(meta, start) {
|
|
1132
|
+
const opening = meta[start];
|
|
1133
|
+
if (opening === '"' || opening === "'") {
|
|
1134
|
+
let close = start + 1;
|
|
1135
|
+
while (close < meta.length && meta[close] !== opening) close += meta[close] === "\\" && meta[close + 1] === opening ? 2 : 1;
|
|
1136
|
+
return close < meta.length ? close + 1 : close;
|
|
1137
|
+
}
|
|
1138
|
+
if (opening !== "{") return void 0;
|
|
1139
|
+
let depth = 0;
|
|
1140
|
+
let quote = "";
|
|
1141
|
+
for (let index = start; index < meta.length; index++) {
|
|
1142
|
+
const char = meta[index];
|
|
1143
|
+
if (quote) {
|
|
1144
|
+
if (char === quote) quote = "";
|
|
1145
|
+
} else if (char === '"' || char === "'") quote = char;
|
|
1146
|
+
else if (char === "{") depth++;
|
|
1147
|
+
else if (char === "}" && --depth === 0) return index + 1;
|
|
1148
|
+
}
|
|
1149
|
+
return meta.length;
|
|
1150
|
+
}
|
|
1151
|
+
function tokens(meta) {
|
|
1152
|
+
const result = [];
|
|
1153
|
+
let index = 0;
|
|
1154
|
+
while (index < meta.length) {
|
|
1155
|
+
if (/\s/.test(meta[index])) {
|
|
1156
|
+
index++;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
const start = index;
|
|
1160
|
+
const delimited = delimitedEnd(meta, index);
|
|
1161
|
+
if (delimited !== void 0) {
|
|
1162
|
+
result.push({ start, end: delimited, value: meta.slice(start, delimited), quoted: meta[start] !== "{", group: meta[start] === "{" });
|
|
1163
|
+
index = delimited;
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const key = /^([A-Za-z][\w-]*)=/.exec(meta.slice(index));
|
|
1167
|
+
if (key) {
|
|
1168
|
+
index += key[0].length;
|
|
1169
|
+
const groupEnd = delimitedEnd(meta, index);
|
|
1170
|
+
if (groupEnd !== void 0) index = groupEnd;
|
|
1171
|
+
else while (index < meta.length && !/\s/.test(meta[index])) index++;
|
|
1172
|
+
result.push({ start, end: index, key: key[1], value: meta.slice(start, index) });
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
while (index < meta.length && !/\s/.test(meta[index])) {
|
|
1176
|
+
if (index > start && meta[index] === "{" && /^\{[\d,\s-]*\}/.test(meta.slice(index))) break;
|
|
1177
|
+
index++;
|
|
1178
|
+
}
|
|
1179
|
+
result.push({ start, end: index, value: meta.slice(start, index) });
|
|
1180
|
+
}
|
|
1181
|
+
return result;
|
|
1182
|
+
}
|
|
1183
|
+
function normalizeMintlifyCodeFences(body, warn) {
|
|
1184
|
+
const tree = unified().use(remarkParse).parse(body);
|
|
1185
|
+
const edits = [];
|
|
1186
|
+
const removed = /* @__PURE__ */ new Set();
|
|
1187
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
1188
|
+
const supported = /* @__PURE__ */ new Set(["title", "filename", "framework", "tag", "icon", "highlight", "focus", "wrap", "lines", "expandable", "nocopy"]);
|
|
1189
|
+
function visit(node) {
|
|
1190
|
+
if (node.type === "code" && node.position?.start.offset !== void 0) {
|
|
1191
|
+
const start = node.position.start.offset;
|
|
1192
|
+
const lineEnd = body.indexOf("\n", start);
|
|
1193
|
+
const end = lineEnd < 0 ? body.length : lineEnd;
|
|
1194
|
+
const line = body.slice(start, end);
|
|
1195
|
+
const opening = /^([`~]{3,}[^\s]*)([ \t]+)(.*?)(\r?)$/.exec(line);
|
|
1196
|
+
if (opening) {
|
|
1197
|
+
const meta = opening[3];
|
|
1198
|
+
const kept = tokens(meta).filter((token) => {
|
|
1199
|
+
if (token.key === "theme" || !token.key && !token.quoted && !token.group && token.value === "twoslash") {
|
|
1200
|
+
removed.add(token.key ?? token.value);
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
if (token.key && !supported.has(token.key)) unknown.add(token.key);
|
|
1204
|
+
return true;
|
|
1205
|
+
});
|
|
1206
|
+
const normalized = kept.map((token) => meta.slice(token.start, token.end)).join(" ");
|
|
1207
|
+
if (normalized !== meta && kept.length !== tokens(meta).length) edits.push({ start, end, value: `${opening[1]}${normalized ? ` ${normalized}` : ""}${opening[4]}` });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
for (const child of node.children ?? []) visit(child);
|
|
1211
|
+
}
|
|
1212
|
+
visit(tree);
|
|
1213
|
+
if (removed.size) warn?.(`Removed unsupported Mintlify code-fence options: ${[...removed].join(", ")}. Code examples and supported display options were preserved.`);
|
|
1214
|
+
if (unknown.size) warn?.(`Unsupported Mintlify code-fence options are ignored: ${[...unknown].join(", ")}. Review these renderer-specific options.`);
|
|
1215
|
+
return edits.sort((a, b) => b.start - a.start).reduce((text3, edit) => text3.slice(0, edit.start) + edit.value + text3.slice(edit.end), body);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1128
1218
|
// src/mdx.ts
|
|
1129
1219
|
function titleFromId(id) {
|
|
1130
1220
|
const value = id.split("/").at(-1) ?? id;
|
|
1131
1221
|
if (value === "introduction") return "Introduction";
|
|
1132
1222
|
return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1133
1223
|
}
|
|
1134
|
-
var descriptionParser =
|
|
1224
|
+
var descriptionParser = unified2().use(remarkParse2).use(remarkGfm).use(remarkMdx);
|
|
1135
1225
|
function plainText(node) {
|
|
1136
1226
|
if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
|
|
1137
1227
|
if (node.type === "image") return node.alt ?? "";
|
|
@@ -2648,13 +2738,41 @@ function externalPageUrl(value) {
|
|
|
2648
2738
|
}
|
|
2649
2739
|
function parseMarkdownPage(input) {
|
|
2650
2740
|
const parsed = parseFrontmatter(input.raw);
|
|
2741
|
+
if (input.platform === "mintlify") {
|
|
2742
|
+
const supported = /* @__PURE__ */ new Set([
|
|
2743
|
+
"title",
|
|
2744
|
+
"description",
|
|
2745
|
+
"sidebarTitle",
|
|
2746
|
+
"navTitle",
|
|
2747
|
+
"icon",
|
|
2748
|
+
"iconType",
|
|
2749
|
+
"tag",
|
|
2750
|
+
"badge",
|
|
2751
|
+
"keywords",
|
|
2752
|
+
"mode",
|
|
2753
|
+
"hidden",
|
|
2754
|
+
"noindex",
|
|
2755
|
+
"url",
|
|
2756
|
+
"openapi",
|
|
2757
|
+
"api",
|
|
2758
|
+
"authMethod",
|
|
2759
|
+
"playground",
|
|
2760
|
+
// Access settings are enforced by the repository classifier, never lost here.
|
|
2761
|
+
"groups",
|
|
2762
|
+
"public",
|
|
2763
|
+
...PAGE_META_FIELDS.map(([field]) => field)
|
|
2764
|
+
]);
|
|
2765
|
+
const dropped = Object.keys(parsed.data).filter((key) => !supported.has(key));
|
|
2766
|
+
if (dropped.length) input.warn?.(`Unsupported Mintlify frontmatter fields were omitted: ${dropped.join(", ")}. Review their behavior before publishing.`);
|
|
2767
|
+
}
|
|
2651
2768
|
const fallbackIdentity = {
|
|
2652
2769
|
id: input.id,
|
|
2653
2770
|
navigationId: input.navigationId ?? input.id,
|
|
2654
2771
|
...input.locale ? { locale: input.locale } : {}
|
|
2655
2772
|
};
|
|
2656
2773
|
const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
|
|
2657
|
-
const
|
|
2774
|
+
const normalized = normalizeMdx(parsed.content, input.platform);
|
|
2775
|
+
const body = removeUndefinedExpressions(injectReactHookImports(input.platform === "mintlify" ? normalizeMintlifyCodeFences(normalized, input.warn) : normalized), input.warn).trim();
|
|
2658
2776
|
const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
|
|
2659
2777
|
const heading = leadingPageHeading(body);
|
|
2660
2778
|
const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title ?? titleFromId(identity.navigationId);
|
|
@@ -2817,7 +2935,7 @@ function youtubeEmbedVideoId(node, specifier) {
|
|
|
2817
2935
|
}
|
|
2818
2936
|
return void 0;
|
|
2819
2937
|
}
|
|
2820
|
-
var parser =
|
|
2938
|
+
var parser = unified3().use(remarkParse3).use(remarkMdx2);
|
|
2821
2939
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs"]);
|
|
2822
2940
|
var DATA_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".css", ".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".woff", ".woff2"]);
|
|
2823
2941
|
var RESCUABLE_ASSET_EXTENSIONS = /* @__PURE__ */ new Set([".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".ico", ".docx", ".pdf"]);
|
|
@@ -4495,8 +4613,8 @@ function localReference(value) {
|
|
|
4495
4613
|
}
|
|
4496
4614
|
function jsonPointer(root, pointer) {
|
|
4497
4615
|
if (!pointer || pointer === "#") return root;
|
|
4498
|
-
const
|
|
4499
|
-
return
|
|
4616
|
+
const tokens2 = pointer.replace(/^#\/?/, "").split("/").filter(Boolean);
|
|
4617
|
+
return tokens2.reduce((current, token) => {
|
|
4500
4618
|
if (Array.isArray(current)) {
|
|
4501
4619
|
const index = Number(token);
|
|
4502
4620
|
return Number.isSafeInteger(index) && index >= 0 ? current[index] : void 0;
|
|
@@ -6416,8 +6534,8 @@ function docusaurusSlugifySegment(value) {
|
|
|
6416
6534
|
decoded = decodeURIComponent(value);
|
|
6417
6535
|
} catch {
|
|
6418
6536
|
}
|
|
6419
|
-
const
|
|
6420
|
-
return
|
|
6537
|
+
const slug3 = decoded.replace(/\.(?:html?|mdx?)$/i, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/(^-|-$)/g, "");
|
|
6538
|
+
return slug3 === "." || slug3 === ".." ? "" : slug3;
|
|
6421
6539
|
}
|
|
6422
6540
|
function docusaurusRouteId(value) {
|
|
6423
6541
|
const segments = normalizeDocId(value).split("/").map(docusaurusSlugifySegment).filter(Boolean);
|
|
@@ -6435,11 +6553,11 @@ function storageIdForRoute(routeId) {
|
|
|
6435
6553
|
function titleCase3(value) {
|
|
6436
6554
|
return stripNumberPrefix(value).replace(/sidebar$/i, "").split(/[-_]/).filter(Boolean).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
6437
6555
|
}
|
|
6438
|
-
function routeFromSlug(
|
|
6439
|
-
if (
|
|
6440
|
-
if (
|
|
6556
|
+
function routeFromSlug(slug3, fallback, sourceDirectory) {
|
|
6557
|
+
if (slug3 === "/") return "introduction";
|
|
6558
|
+
if (slug3.startsWith("/")) return docusaurusRouteId(slug3);
|
|
6441
6559
|
const fallbackDirectory = sourceDirectory ?? (fallback === "introduction" ? "" : posix3.dirname(fallback));
|
|
6442
|
-
const resolved = posix3.normalize(posix3.join(fallbackDirectory,
|
|
6560
|
+
const resolved = posix3.normalize(posix3.join(fallbackDirectory, slug3));
|
|
6443
6561
|
if (resolved === ".." || resolved.startsWith("../")) return null;
|
|
6444
6562
|
return docusaurusRouteId(resolved);
|
|
6445
6563
|
}
|
|
@@ -6560,9 +6678,9 @@ function expandDocusaurusDocCardLists(pages, config, sourceRoutes) {
|
|
|
6560
6678
|
const target2 = pageByRoute.get(item);
|
|
6561
6679
|
return [{ title: target2?.title ?? item.split("/").at(-1) ?? item, route: item }];
|
|
6562
6680
|
}
|
|
6563
|
-
const
|
|
6681
|
+
const slug3 = slugifySegment(item.group);
|
|
6564
6682
|
const routes = [...item.pages].flatMap((child) => typeof child === "string" ? [child] : []);
|
|
6565
|
-
const route = routes.find((candidate) => candidate ===
|
|
6683
|
+
const route = routes.find((candidate) => candidate === slug3 || candidate.endsWith(`/${slug3}`)) ?? firstRoute(item.pages);
|
|
6566
6684
|
return route ? [{ title: item.group, route }] : [];
|
|
6567
6685
|
});
|
|
6568
6686
|
const markup = cards.length ? `<CardGroup>
|
|
@@ -7395,7 +7513,7 @@ function readDocusaurusPresentation(repositoryRoot) {
|
|
|
7395
7513
|
}
|
|
7396
7514
|
return styles.join(";");
|
|
7397
7515
|
};
|
|
7398
|
-
let
|
|
7516
|
+
let tokens2 = "";
|
|
7399
7517
|
const prismStart = propertyStart(config, "prism");
|
|
7400
7518
|
const prism = prismStart < 0 ? null : matchingObjectLiteral(config, prismStart);
|
|
7401
7519
|
const themeStart = prism ? propertyStart(prism, "theme") : -1;
|
|
@@ -7421,7 +7539,7 @@ function readDocusaurusPresentation(repositoryRoot) {
|
|
|
7421
7539
|
const hex = typeof style.style?.color === "string" ? cssColorToHex(style.style.color) : void 0;
|
|
7422
7540
|
if (tokenNames[type] && hex) colors.set(`--shiki-token-${tokenNames[type]}`, hex);
|
|
7423
7541
|
}
|
|
7424
|
-
|
|
7542
|
+
tokens2 = [...colors].map(([name, hex]) => `${name}:${hex}`).join(";");
|
|
7425
7543
|
}
|
|
7426
7544
|
} catch {
|
|
7427
7545
|
}
|
|
@@ -7449,7 +7567,7 @@ body{font-family:${safeBodyFont}}
|
|
|
7449
7567
|
.thally-docs-prose{line-height:1.5;color:var(--docs-ink)}
|
|
7450
7568
|
.thally-docs-prose p{margin-block:0 1rem}
|
|
7451
7569
|
.thally-docs-prose a{color:var(--docs-accent)}
|
|
7452
|
-
.thally-docs-code{${
|
|
7570
|
+
.thally-docs-code{${tokens2};background:var(--docs-code-bg)}
|
|
7453
7571
|
@media(min-width:1024px){
|
|
7454
7572
|
.thally-docs-shell{max-width:none;padding-inline:0}
|
|
7455
7573
|
.thally-docs-sidebar,.thally-docs-sidebar>div{width:300px}
|
|
@@ -7471,10 +7589,45 @@ function unquote(value) {
|
|
|
7471
7589
|
}
|
|
7472
7590
|
function splitOpenApiRef(raw) {
|
|
7473
7591
|
const value = unquote(raw);
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7592
|
+
let firstLineBreak = -1;
|
|
7593
|
+
let lastLineBreak = -1;
|
|
7594
|
+
let lastWhitespace = -1;
|
|
7595
|
+
for (let index = 0; index < value.length; index++) {
|
|
7596
|
+
const character = value[index];
|
|
7597
|
+
if (/\s/.test(character)) lastWhitespace = index;
|
|
7598
|
+
if (character === "\n" || character === "\r" || character === "\u2028" || character === "\u2029") {
|
|
7599
|
+
if (firstLineBreak < 0) firstLineBreak = index;
|
|
7600
|
+
lastLineBreak = index;
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
let bare = null;
|
|
7604
|
+
let previousEnd = 0;
|
|
7605
|
+
let start = 0;
|
|
7606
|
+
while (start < value.length) {
|
|
7607
|
+
let end = start;
|
|
7608
|
+
let letters = true;
|
|
7609
|
+
while (end < value.length && !/\s/.test(value[end])) {
|
|
7610
|
+
const code = value.charCodeAt(end);
|
|
7611
|
+
if (!(code >= 65 && code <= 90) && !(code >= 97 && code <= 122)) letters = false;
|
|
7612
|
+
end++;
|
|
7613
|
+
}
|
|
7614
|
+
let argument = end;
|
|
7615
|
+
while (argument < value.length && /\s/.test(value[argument])) argument++;
|
|
7616
|
+
const hasArgument = argument > end && argument < value.length;
|
|
7617
|
+
const method = letters && value[argument] === "/" && lastLineBreak < argument;
|
|
7618
|
+
const webhook = end - start === 7 && value.slice(start, end).toLowerCase() === "webhook" && value[argument] !== "/" && lastWhitespace < argument;
|
|
7619
|
+
if (hasArgument && (method || webhook)) {
|
|
7620
|
+
if (start === 0) bare = { operation: value };
|
|
7621
|
+
else {
|
|
7622
|
+
const specRef = unquote(value.slice(0, previousEnd));
|
|
7623
|
+
return { ...specRef ? { specRef } : {}, operation: value.slice(start) };
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
previousEnd = end;
|
|
7627
|
+
if (firstLineBreak >= 0 && firstLineBreak < previousEnd) break;
|
|
7628
|
+
start = argument;
|
|
7629
|
+
}
|
|
7630
|
+
return bare;
|
|
7478
7631
|
}
|
|
7479
7632
|
function canonicalSpecRef(specRef) {
|
|
7480
7633
|
return specRef.trim().split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^(?:\.?\/+)+/, "");
|
|
@@ -7628,11 +7781,246 @@ import { isAbsolute as isAbsolute2, join, relative as relative5, resolve as reso
|
|
|
7628
7781
|
// src/url.ts
|
|
7629
7782
|
import { load as load4 } from "cheerio";
|
|
7630
7783
|
import remarkMdx4 from "remark-mdx";
|
|
7631
|
-
import
|
|
7784
|
+
import remarkParse5 from "remark-parse";
|
|
7632
7785
|
import TurndownService from "turndown";
|
|
7633
|
-
import { unified as
|
|
7786
|
+
import { unified as unified5 } from "unified";
|
|
7634
7787
|
import { parse as parseYaml5, stringify as stringifyYaml2 } from "yaml";
|
|
7635
7788
|
|
|
7789
|
+
// src/url-openapi.ts
|
|
7790
|
+
import { createHash as createHash2 } from "crypto";
|
|
7791
|
+
|
|
7792
|
+
// src/remote-spec.ts
|
|
7793
|
+
import { lookup } from "dns/promises";
|
|
7794
|
+
import { request } from "https";
|
|
7795
|
+
import { isIP } from "net";
|
|
7796
|
+
var isWithheld = (node) => [node["x-hidden"], node["x-excluded"]].some((value) => value === true || value === "true");
|
|
7797
|
+
var MAX_SPEC_BYTES = 25e6;
|
|
7798
|
+
var MAX_REDIRECTS = 3;
|
|
7799
|
+
function isPublicAddress(address, family) {
|
|
7800
|
+
if (family === 4) {
|
|
7801
|
+
const parts = address.split(".").map(Number);
|
|
7802
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
7803
|
+
const [a, b] = parts;
|
|
7804
|
+
return !(a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && (b === 0 || b === 168) || a === 198 && (b === 18 || b === 19) || a === 203 && b === 0 || a === 198 && b === 51);
|
|
7805
|
+
}
|
|
7806
|
+
return family === 6 && /^[23][0-9a-f]{0,3}:/i.test(address) && !/^2001:db8:/i.test(address);
|
|
7807
|
+
}
|
|
7808
|
+
function validatedSpecUrl(value) {
|
|
7809
|
+
const url = new URL(value);
|
|
7810
|
+
if (url.protocol !== "https:" || url.username || url.password || isIP(url.hostname)) {
|
|
7811
|
+
throw new Error("Remote OpenAPI references must use an HTTPS domain without credentials or an IP literal.");
|
|
7812
|
+
}
|
|
7813
|
+
url.hash = "";
|
|
7814
|
+
return url;
|
|
7815
|
+
}
|
|
7816
|
+
async function getPinnedAddress(url) {
|
|
7817
|
+
const addresses = await lookup(url.hostname, { all: true });
|
|
7818
|
+
if (addresses.length === 0 || addresses.some((candidate) => !isPublicAddress(candidate.address, candidate.family))) {
|
|
7819
|
+
throw new Error("The OpenAPI host did not resolve exclusively to public addresses.");
|
|
7820
|
+
}
|
|
7821
|
+
return addresses[0];
|
|
7822
|
+
}
|
|
7823
|
+
async function requestPinnedSpec(url) {
|
|
7824
|
+
const pinned = await getPinnedAddress(url);
|
|
7825
|
+
return new Promise((resolve5, reject) => {
|
|
7826
|
+
const req = request(url, {
|
|
7827
|
+
headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
|
|
7828
|
+
signal: AbortSignal.timeout(3e4),
|
|
7829
|
+
// Node's dual-stack connector asks lookup for `all: true`; that
|
|
7830
|
+
// callback must receive address records, not a single address string.
|
|
7831
|
+
lookup: (_hostname, options, callback) => {
|
|
7832
|
+
if (typeof options === "object" && options.all) callback(null, [pinned]);
|
|
7833
|
+
else callback(null, pinned.address, pinned.family);
|
|
7834
|
+
}
|
|
7835
|
+
}, (response) => {
|
|
7836
|
+
const status = response.statusCode ?? 0;
|
|
7837
|
+
if (status >= 300 && status < 400) {
|
|
7838
|
+
response.resume();
|
|
7839
|
+
resolve5({ status, location: response.headers.location, body: Buffer.alloc(0) });
|
|
7840
|
+
return;
|
|
7841
|
+
}
|
|
7842
|
+
if (status < 200 || status >= 300) {
|
|
7843
|
+
response.resume();
|
|
7844
|
+
reject(new Error(`OpenAPI server returned ${status}.`));
|
|
7845
|
+
return;
|
|
7846
|
+
}
|
|
7847
|
+
const statedBytes = Number(response.headers["content-length"] ?? 0);
|
|
7848
|
+
if (statedBytes > MAX_SPEC_BYTES) {
|
|
7849
|
+
response.destroy();
|
|
7850
|
+
reject(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
const chunks = [];
|
|
7854
|
+
let bytes = 0;
|
|
7855
|
+
response.on("data", (chunk) => {
|
|
7856
|
+
bytes += chunk.length;
|
|
7857
|
+
if (bytes > MAX_SPEC_BYTES) {
|
|
7858
|
+
response.destroy(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7861
|
+
chunks.push(chunk);
|
|
7862
|
+
});
|
|
7863
|
+
response.on("end", () => resolve5({ status, body: Buffer.concat(chunks) }));
|
|
7864
|
+
response.on("error", reject);
|
|
7865
|
+
});
|
|
7866
|
+
req.on("error", reject);
|
|
7867
|
+
req.end();
|
|
7868
|
+
});
|
|
7869
|
+
}
|
|
7870
|
+
async function downloadSpec(value, fetcher) {
|
|
7871
|
+
let url = validatedSpecUrl(value);
|
|
7872
|
+
if (fetcher) {
|
|
7873
|
+
const response = await fetcher(url, { accept: "application/json, application/yaml, text/yaml" });
|
|
7874
|
+
validatedSpecUrl(response.finalUrl.toString());
|
|
7875
|
+
const body = Buffer.from(response.body);
|
|
7876
|
+
if (body.length > MAX_SPEC_BYTES) throw new Error("Remote OpenAPI spec exceeds the 25 MB import limit.");
|
|
7877
|
+
return body;
|
|
7878
|
+
}
|
|
7879
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
7880
|
+
const response = await requestPinnedSpec(url);
|
|
7881
|
+
if (response.status >= 300 && response.status < 400) {
|
|
7882
|
+
if (!response.location || hop === MAX_REDIRECTS) throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
7883
|
+
url = validatedSpecUrl(new URL(response.location, url).toString());
|
|
7884
|
+
continue;
|
|
7885
|
+
}
|
|
7886
|
+
return response.body;
|
|
7887
|
+
}
|
|
7888
|
+
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
7889
|
+
}
|
|
7890
|
+
function parseOpenApi(parsed) {
|
|
7891
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
7892
|
+
const document = parsed;
|
|
7893
|
+
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
7894
|
+
throw new Error("Remote specification is not OpenAPI 3.x.");
|
|
7895
|
+
}
|
|
7896
|
+
if (!document.paths || typeof document.paths !== "object" || Array.isArray(document.paths)) {
|
|
7897
|
+
throw new Error("Remote OpenAPI spec has no paths object.");
|
|
7898
|
+
}
|
|
7899
|
+
const paths = Object.entries(document.paths);
|
|
7900
|
+
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
7901
|
+
const operations = [];
|
|
7902
|
+
for (const [path, value] of paths) {
|
|
7903
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || isWithheld(value)) continue;
|
|
7904
|
+
for (const [method, raw] of Object.entries(value)) {
|
|
7905
|
+
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
7906
|
+
const operation = raw;
|
|
7907
|
+
if (isWithheld(operation)) continue;
|
|
7908
|
+
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
7909
|
+
operations.push({
|
|
7910
|
+
method: method.toLowerCase(),
|
|
7911
|
+
path,
|
|
7912
|
+
tag: typeof tags[0] === "string" ? tags[0] : "API Reference",
|
|
7913
|
+
summary: typeof operation.summary === "string" ? operation.summary : String(operation.operationId ?? "")
|
|
7914
|
+
});
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7917
|
+
return operations;
|
|
7918
|
+
}
|
|
7919
|
+
|
|
7920
|
+
// src/url-openapi.ts
|
|
7921
|
+
var slug = (value) => value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7922
|
+
async function hydrateUrlOpenApi(bundle, specUrl, fetcher) {
|
|
7923
|
+
try {
|
|
7924
|
+
const content = withoutByteOrderMark(await downloadSpec(specUrl, fetcher));
|
|
7925
|
+
const { document, format } = parseSpecDocument(content.toString("utf8"));
|
|
7926
|
+
const operations = parseOpenApi(document);
|
|
7927
|
+
const path = `openapi/openapi-${createHash2("sha256").update(specUrl).digest("hex").slice(0, 12)}.${format}`;
|
|
7928
|
+
const byRoute = /* @__PURE__ */ new Map();
|
|
7929
|
+
for (const operation of operations) {
|
|
7930
|
+
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
7931
|
+
if (!slug(operation.summary)) continue;
|
|
7932
|
+
byRoute.set(key, byRoute.has(key) ? null : operation);
|
|
7933
|
+
}
|
|
7934
|
+
const represented = /* @__PURE__ */ new Set();
|
|
7935
|
+
const visible = new Set(operations.map((operation) => operationKey2(`${operation.method} ${operation.path}`)));
|
|
7936
|
+
const assets = bundle.assets.filter((asset) => /\.(?:json|ya?ml)$/i.test(asset.path)).map((asset) => ({ ...asset, sourcePath: canonicalSpecRef(asset.path), filename: specRefBaseName(asset.path) }));
|
|
7937
|
+
const published = /* @__PURE__ */ new Map();
|
|
7938
|
+
const defaultSource = [...bundle.docsConfig.tabs.filter((tab) => !tab.hidden), ...bundle.docsConfig.tabs.filter((tab) => tab.hidden)].find((tab) => tab.api)?.api?.source;
|
|
7939
|
+
const isExistingBinding = (raw) => {
|
|
7940
|
+
const ref = splitOpenApiRef(raw);
|
|
7941
|
+
if (!ref) return void 0;
|
|
7942
|
+
const key = operationKey2(ref.operation);
|
|
7943
|
+
if (!visible.has(key)) return void 0;
|
|
7944
|
+
const source = ref.specRef ?? defaultSource;
|
|
7945
|
+
if (!source) return void 0;
|
|
7946
|
+
const resolved = resolveSpecRef(source, assets, false);
|
|
7947
|
+
if (resolved.kind !== "match") return void 0;
|
|
7948
|
+
const asset = resolved.spec;
|
|
7949
|
+
if (!published.has(asset.path)) {
|
|
7950
|
+
try {
|
|
7951
|
+
const original = parseSpecDocument(Buffer.from(asset.content).toString("utf8")).document;
|
|
7952
|
+
published.set(asset.path, new Set(parseOpenApi(original).map((operation) => operationKey2(`${operation.method} ${operation.path}`))));
|
|
7953
|
+
} catch {
|
|
7954
|
+
published.set(asset.path, /* @__PURE__ */ new Set());
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7957
|
+
return published.get(asset.path).has(key) ? { operation: ref.operation, source: asset.path } : void 0;
|
|
7958
|
+
};
|
|
7959
|
+
const reboundSources = /* @__PURE__ */ new Set();
|
|
7960
|
+
const withheld = /* @__PURE__ */ new Set();
|
|
7961
|
+
const kept = /* @__PURE__ */ new Set();
|
|
7962
|
+
const bind = (page, reference) => {
|
|
7963
|
+
const key = operationKey2(reference);
|
|
7964
|
+
represented.add(key);
|
|
7965
|
+
if (page.public === false || page.groups?.length) withheld.add(key);
|
|
7966
|
+
else kept.add(key);
|
|
7967
|
+
return { ...page, openapi: `${path} ${reference}` };
|
|
7968
|
+
};
|
|
7969
|
+
const pages = bundle.pages.map((page) => {
|
|
7970
|
+
if (bundle.sourceKind !== "url") return page;
|
|
7971
|
+
if (page.openapi) {
|
|
7972
|
+
const selected = splitOpenApiRef(page.openapi);
|
|
7973
|
+
if (selected && (selected.specRef === specUrl || selected.specRef === path && assets.some((asset) => asset.path === path)) && visible.has(operationKey2(selected.operation))) {
|
|
7974
|
+
return bind(page, selected.operation);
|
|
7975
|
+
}
|
|
7976
|
+
const bound = isExistingBinding(page.openapi);
|
|
7977
|
+
if (bound) {
|
|
7978
|
+
reboundSources.add(bound.source);
|
|
7979
|
+
return bind(page, bound.operation);
|
|
7980
|
+
}
|
|
7981
|
+
return page;
|
|
7982
|
+
}
|
|
7983
|
+
const operation = byRoute.get(page.navigationId.split("/").slice(-2).join("/").toLowerCase());
|
|
7984
|
+
if (!operation) return page;
|
|
7985
|
+
const reference = `${operation.method.toUpperCase()} ${operation.path}`;
|
|
7986
|
+
return bind(page, reference);
|
|
7987
|
+
});
|
|
7988
|
+
const remaining = operations.map((operation) => `${operation.method.toUpperCase()} ${operation.path}`).filter((reference) => !represented.has(operationKey2(reference)));
|
|
7989
|
+
const pruned = markExcluded(content, [...withheld].filter((key) => !kept.has(key)), [...kept]);
|
|
7990
|
+
const warning = pruned.excluded.length ? { code: "gated-page", source: path, message: sharedSpecMessage(path, pruned.excluded) } : void 0;
|
|
7991
|
+
const api = { source: path, ...represented.size ? remaining.length ? { groups: [{ group: "Endpoints", pages: remaining }] } : { navigation: false } : {} };
|
|
7992
|
+
const stillReferenced = new Set(pages.flatMap((page) => {
|
|
7993
|
+
const source = page.openapi ? splitOpenApiRef(page.openapi)?.specRef ?? defaultSource : void 0;
|
|
7994
|
+
if (!source) return [];
|
|
7995
|
+
const resolved = resolveSpecRef(source, assets, false);
|
|
7996
|
+
return resolved.kind === "match" ? [resolved.spec.path] : [];
|
|
7997
|
+
}));
|
|
7998
|
+
const tabs = bundle.docsConfig.tabs.filter((tab) => {
|
|
7999
|
+
if (!tab.hidden || tab.api?.navigation !== false || tab.pages?.length || tab.groups?.length || tab.href) return true;
|
|
8000
|
+
const source = resolveSpecRef(tab.api.source, assets, false);
|
|
8001
|
+
return source.kind !== "match" || !reboundSources.has(source.spec.path) || stillReferenced.has(source.spec.path);
|
|
8002
|
+
}).map((tab) => ({ ...tab }));
|
|
8003
|
+
const target2 = tabs.find((tab) => tab.api && [path, specUrl].includes(tab.api.source)) ?? tabs.find((tab) => !tab.api && tab.tab.toLowerCase().includes("api"));
|
|
8004
|
+
if (target2) target2.api = api;
|
|
8005
|
+
else {
|
|
8006
|
+
const taken = new Set(tabs.map((tab) => slug(tab.tab)));
|
|
8007
|
+
let label = "API Reference";
|
|
8008
|
+
let suffix = 2;
|
|
8009
|
+
while (taken.has(slug(label))) label = `API Reference ${suffix++}`;
|
|
8010
|
+
tabs.push({ tab: label, api });
|
|
8011
|
+
}
|
|
8012
|
+
return {
|
|
8013
|
+
...bundle,
|
|
8014
|
+
pages,
|
|
8015
|
+
assets: [...bundle.assets.filter((asset) => asset.path !== path), { path, content: pruned.content, projectRelative: true }],
|
|
8016
|
+
docsConfig: { ...bundle.docsConfig, tabs },
|
|
8017
|
+
warnings: warning && !bundle.warnings.some((item) => item.code === warning.code && item.source === warning.source && item.message === warning.message) ? [...bundle.warnings, warning] : bundle.warnings
|
|
8018
|
+
};
|
|
8019
|
+
} catch (error) {
|
|
8020
|
+
return { ...bundle, warnings: [...bundle.warnings, { code: "fetch-failed", source: specUrl, message: `OpenAPI spec could not be imported: ${error instanceof Error ? error.message : String(error)}` }] };
|
|
8021
|
+
}
|
|
8022
|
+
}
|
|
8023
|
+
|
|
7636
8024
|
// src/analytics.ts
|
|
7637
8025
|
var GA4_ID = /^G-[A-Z0-9]{4,20}$/;
|
|
7638
8026
|
var GTM_ID = /^GTM-[A-Z0-9]{4,12}$/;
|
|
@@ -7928,7 +8316,7 @@ function projectMigrationMedia(html, base) {
|
|
|
7928
8316
|
}
|
|
7929
8317
|
|
|
7930
8318
|
// src/url-static-content.ts
|
|
7931
|
-
import { createHash as
|
|
8319
|
+
import { createHash as createHash3 } from "crypto";
|
|
7932
8320
|
import { load as load3 } from "cheerio";
|
|
7933
8321
|
|
|
7934
8322
|
// src/svg-policy.ts
|
|
@@ -8250,7 +8638,7 @@ function projectMigrationSvg(html) {
|
|
|
8250
8638
|
if (svg.attr("xmlns") === void 0) svg.attr("xmlns", "http://www.w3.org/2000/svg");
|
|
8251
8639
|
const content = Buffer.from($.xml(svg));
|
|
8252
8640
|
if (!isPassiveSvg(content)) return null;
|
|
8253
|
-
const sha256 =
|
|
8641
|
+
const sha256 = createHash3("sha256").update(content).digest("hex");
|
|
8254
8642
|
const labelled = (svg.attr("aria-labelledby") ?? "").split(/\s+/).filter(Boolean).map((id) => svg.find("[id]").filter((_index, node) => $(node).attr("id") === id).text().trim()).filter(Boolean);
|
|
8255
8643
|
const alternative = svg.attr("aria-label")?.trim() || labelled.join(" ") || svg.children("title").text().trim() || svg.children("desc").text().trim() || svg.find("text").text().trim();
|
|
8256
8644
|
return { asset: { path: `migration-assets/${sha256}.svg`, content }, alternative };
|
|
@@ -8364,12 +8752,12 @@ function projectCapturedTabs($, root) {
|
|
|
8364
8752
|
}
|
|
8365
8753
|
|
|
8366
8754
|
// src/url-flight-content.ts
|
|
8367
|
-
import { createHash as
|
|
8755
|
+
import { createHash as createHash4 } from "crypto";
|
|
8368
8756
|
import remarkMdx3 from "remark-mdx";
|
|
8369
|
-
import
|
|
8757
|
+
import remarkParse4 from "remark-parse";
|
|
8370
8758
|
import remarkGfm2 from "remark-gfm";
|
|
8371
|
-
import { unified as
|
|
8372
|
-
var parser2 =
|
|
8759
|
+
import { unified as unified4 } from "unified";
|
|
8760
|
+
var parser2 = unified4().use(remarkParse4).use(remarkGfm2).use(remarkMdx3);
|
|
8373
8761
|
var MAX_SOURCE_BYTES = 8e6;
|
|
8374
8762
|
var sourceTrees = /* @__PURE__ */ new Map();
|
|
8375
8763
|
var cachedSourceBytes = 0;
|
|
@@ -8541,7 +8929,7 @@ function sourceText(node) {
|
|
|
8541
8929
|
return node.value ?? (node.children ?? []).map(sourceText).join("");
|
|
8542
8930
|
}
|
|
8543
8931
|
function sourceNodes(source) {
|
|
8544
|
-
const key =
|
|
8932
|
+
const key = createHash4("sha256").update(source).digest("hex");
|
|
8545
8933
|
const cached = sourceTrees.get(key);
|
|
8546
8934
|
if (cached) {
|
|
8547
8935
|
sourceTrees.delete(key);
|
|
@@ -8890,7 +9278,7 @@ function recoverFlightDisclosures($, root, document, article, projectionBudget)
|
|
|
8890
9278
|
if (!id || !target2 || title === null || labelText(title) !== labelText(button.text()) || panel.length !== 1 || panel.attr("role") !== "region" || panel.attr("aria-labelledby") !== id || root.find("[id]").filter((_index, node) => $(node).attr("id") === id).length !== 1 || panel.is('[aria-busy="true"]') || panel.find('[aria-busy="true"],[role="progressbar"]').length) continue;
|
|
8891
9279
|
let html;
|
|
8892
9280
|
if (!panel.text().trim() && !panel.children().length) {
|
|
8893
|
-
const prefix = `thally-flight-${
|
|
9281
|
+
const prefix = `thally-flight-${createHash4("sha256").update(target2).digest("hex").slice(0, 16)}`;
|
|
8894
9282
|
const rendered = flightStaticHtml(document, source.props.children, prefix, projectionBudget);
|
|
8895
9283
|
if (rendered === null || !rendered.trim()) continue;
|
|
8896
9284
|
html = rendered;
|
|
@@ -9129,8 +9517,13 @@ function recoverFlightArticleCode($, root) {
|
|
|
9129
9517
|
|
|
9130
9518
|
// src/redirects.ts
|
|
9131
9519
|
var LISTED = 10;
|
|
9520
|
+
function trimTrailingSlashes2(route) {
|
|
9521
|
+
let end = route.length;
|
|
9522
|
+
while (end > 0 && route[end - 1] === "/") end--;
|
|
9523
|
+
return route.slice(0, end);
|
|
9524
|
+
}
|
|
9132
9525
|
function exactRoute(route) {
|
|
9133
|
-
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route
|
|
9526
|
+
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? trimTrailingSlashes2(route) || "/" : null;
|
|
9134
9527
|
}
|
|
9135
9528
|
function literalRoute(route) {
|
|
9136
9529
|
return exactRoute(route)?.toLowerCase() ?? null;
|
|
@@ -9393,8 +9786,8 @@ var MINTLIFY_ARTICLE_LABELS = [
|
|
|
9393
9786
|
{ baseUrl: "Select base URL", permalink: "Navigate to header" },
|
|
9394
9787
|
{ baseUrl: "\u9009\u62E9\u57FA\u7840URL", permalink: "\u5BFC\u822A\u5230\u6807\u9898" }
|
|
9395
9788
|
];
|
|
9396
|
-
var remoteMarkdownParser =
|
|
9397
|
-
var embeddedOpenApiParser =
|
|
9789
|
+
var remoteMarkdownParser = unified5().use(remarkParse5).use(remarkMdx4);
|
|
9790
|
+
var embeddedOpenApiParser = unified5().use(remarkParse5);
|
|
9398
9791
|
function validateMigrationUrl(value) {
|
|
9399
9792
|
let url;
|
|
9400
9793
|
try {
|
|
@@ -10010,9 +10403,13 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
|
|
|
10010
10403
|
return `/${target2.search}${target2.hash}`;
|
|
10011
10404
|
}
|
|
10012
10405
|
let path = target2.pathname;
|
|
10406
|
+
const isMarkdown = path.endsWith(".md");
|
|
10407
|
+
const machinePath = path.replace(/\/+$/, "");
|
|
10408
|
+
if (["/llms.txt", "/llms-full.txt", "/openapi.json", "/openapi.yaml", "/robots.txt", "/sitemap.xml"].includes(machinePath)) return `${machinePath}${target2.search}${target2.hash}`;
|
|
10013
10409
|
if (scopePath !== "/" && (path === scopePath || path.startsWith(`${scopePath}/`))) {
|
|
10014
10410
|
path = path.slice(scopePath.length);
|
|
10015
10411
|
}
|
|
10412
|
+
if (["/llms.txt", "/llms-full.txt", "/openapi.json", "/openapi.yaml", "/robots.txt", "/sitemap.xml"].includes(path)) return `${path}${target2.search}${target2.hash}`;
|
|
10016
10413
|
const id = pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
|
|
10017
10414
|
const localizedId = id ? canonicalLocalizedPageId(id) : id;
|
|
10018
10415
|
const destinationId = localizedId === sourceHomePageId ? "introduction" : localizedId;
|
|
@@ -10022,7 +10419,7 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
|
|
|
10022
10419
|
}
|
|
10023
10420
|
if (importedDestinationId) {
|
|
10024
10421
|
const destination = importedDestinationId === "introduction" ? "/" : `/${importedDestinationId}`;
|
|
10025
|
-
return `${destination}${target2.search}${target2.hash}`;
|
|
10422
|
+
return `${destination === "/" && isMarkdown ? "/introduction" : destination}${isMarkdown ? ".md" : ""}${target2.search}${target2.hash}`;
|
|
10026
10423
|
}
|
|
10027
10424
|
return target2.toString();
|
|
10028
10425
|
} catch {
|
|
@@ -11197,9 +11594,9 @@ async function migrateUrl(options) {
|
|
|
11197
11594
|
]
|
|
11198
11595
|
};
|
|
11199
11596
|
}
|
|
11200
|
-
if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
|
|
11597
|
+
if (platform === "mintlify") docsConfig = { ...addMintlifyDirectoryRedirects(docsConfig, pages), markdown: { enabled: true } };
|
|
11201
11598
|
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), pages, warnings);
|
|
11202
|
-
|
|
11599
|
+
const bundle = {
|
|
11203
11600
|
sourceUrl: source.toString(),
|
|
11204
11601
|
sourceKind: "url",
|
|
11205
11602
|
platform,
|
|
@@ -11219,6 +11616,20 @@ async function migrateUrl(options) {
|
|
|
11219
11616
|
skipped: Math.max(0, visited.size - pages.length)
|
|
11220
11617
|
}
|
|
11221
11618
|
};
|
|
11619
|
+
const sourceHtml = scopedHtmlProbe ? load4(scopedHtmlProbe.body) : void 0;
|
|
11620
|
+
const linkedSpecs = sourceHtml && scopedHtmlProbe && /html/i.test(scopedHtmlProbe.contentType) ? [...new Set(sourceHtml('link[rel="service-desc"][href], a[href]').map((_index, element) => {
|
|
11621
|
+
const href = sourceHtml(element).attr("href");
|
|
11622
|
+
if (!href) return null;
|
|
11623
|
+
try {
|
|
11624
|
+
const url = new URL(href, scopedHtmlProbe.finalUrl);
|
|
11625
|
+
return url.protocol === "https:" && /\/(?:openapi|swagger)\.(?:json|ya?ml)$/i.test(url.pathname) ? url.toString() : null;
|
|
11626
|
+
} catch {
|
|
11627
|
+
return null;
|
|
11628
|
+
}
|
|
11629
|
+
}).get())] : [];
|
|
11630
|
+
const specUrl = options.openapiUrl ?? (!openApiAsset && linkedSpecs.length === 1 ? linkedSpecs[0] : void 0);
|
|
11631
|
+
if (!options.openapiUrl && linkedSpecs.length > 1) bundle.warnings.push({ code: "unsupported-config", message: "Multiple OpenAPI specifications were linked by the source. Select one explicitly with --openapi <https-url>." });
|
|
11632
|
+
return specUrl ? hydrateUrlOpenApi(bundle, specUrl, options.fetcher) : bundle;
|
|
11222
11633
|
}
|
|
11223
11634
|
|
|
11224
11635
|
// src/source-refs.ts
|
|
@@ -12370,12 +12781,12 @@ function classifyApiSpec(path) {
|
|
|
12370
12781
|
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
12371
12782
|
}
|
|
12372
12783
|
var SPEC_DIRECTORY = "openapi";
|
|
12373
|
-
var
|
|
12784
|
+
var MAX_SPEC_BYTES2 = 5e7;
|
|
12374
12785
|
function readSpecFile(absolutePath, relativePath, warnings) {
|
|
12375
|
-
if (lstatSync6(absolutePath).size >
|
|
12786
|
+
if (lstatSync6(absolutePath).size > MAX_SPEC_BYTES2) {
|
|
12376
12787
|
warnings.push({
|
|
12377
12788
|
code: "skipped-file",
|
|
12378
|
-
message: `The OpenAPI spec "${relativePath}" is larger than ${
|
|
12789
|
+
message: `The OpenAPI spec "${relativePath}" is larger than ${MAX_SPEC_BYTES2 / 1e6} MB and was not migrated; split it or reduce its size.`,
|
|
12379
12790
|
source: relativePath
|
|
12380
12791
|
});
|
|
12381
12792
|
return void 0;
|
|
@@ -12601,7 +13012,7 @@ function apiTabSpecIds(tabs) {
|
|
|
12601
13012
|
return tabs.filter((tab) => !tab.hidden && tab.api).map((tab, index) => ({ tab, specId: index === 0 ? "default" : slugifyApiSpecId(tab.tab) }));
|
|
12602
13013
|
}
|
|
12603
13014
|
var flagged = (value) => value === true || value === "true";
|
|
12604
|
-
var
|
|
13015
|
+
var isWithheld2 = (node) => flagged(node["x-hidden"]) || flagged(node["x-excluded"]);
|
|
12605
13016
|
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12606
13017
|
function resolveLocalSpecRef(document, ref) {
|
|
12607
13018
|
if (ref !== "#" && !ref.startsWith("#/")) return void 0;
|
|
@@ -12628,7 +13039,7 @@ function viewSpecPathItem(document, entry) {
|
|
|
12628
13039
|
}
|
|
12629
13040
|
const item = Object.assign({}, ...[...chain].reverse());
|
|
12630
13041
|
delete item.$ref;
|
|
12631
|
-
return { item, withheld: chain.some(
|
|
13042
|
+
return { item, withheld: chain.some(isWithheld2) };
|
|
12632
13043
|
}
|
|
12633
13044
|
function parseOpenApiOperationsForLinking(content) {
|
|
12634
13045
|
const document = readSpecDocument(content);
|
|
@@ -12652,7 +13063,7 @@ function visibleOperations(document, entries) {
|
|
|
12652
13063
|
const operation = item[method];
|
|
12653
13064
|
if (!operation || typeof operation !== "object") continue;
|
|
12654
13065
|
const op = operation;
|
|
12655
|
-
if (
|
|
13066
|
+
if (isWithheld2(op)) continue;
|
|
12656
13067
|
const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
|
|
12657
13068
|
operations.push({
|
|
12658
13069
|
method,
|
|
@@ -12666,6 +13077,28 @@ function visibleOperations(document, entries) {
|
|
|
12666
13077
|
}
|
|
12667
13078
|
return operations;
|
|
12668
13079
|
}
|
|
13080
|
+
function migrationApiRoutes(bundle) {
|
|
13081
|
+
const routes = /* @__PURE__ */ new Set();
|
|
13082
|
+
for (const { tab, specId } of apiTabSpecIds(bundle.docsConfig.tabs)) {
|
|
13083
|
+
const source = tab.api.source.replace(/^\//, "");
|
|
13084
|
+
const asset = bundle.assets.find((entry) => entry.path.replace(/^\//, "") === source);
|
|
13085
|
+
if (!asset) continue;
|
|
13086
|
+
const document = readSpecDocument(Buffer.from(asset.content));
|
|
13087
|
+
if (!isRecord2(document)) continue;
|
|
13088
|
+
const operations = visibleOperations(document, document.paths);
|
|
13089
|
+
const webhooks = visibleOperations(document, document.webhooks);
|
|
13090
|
+
if (specId === "default" && (operations.length || webhooks.length)) routes.add("/api");
|
|
13091
|
+
for (const operation of operations) {
|
|
13092
|
+
routes.add(`/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`);
|
|
13093
|
+
}
|
|
13094
|
+
for (const operation of webhooks) {
|
|
13095
|
+
const segments = thallyOperationSlugSegments(operation.path, operation.method);
|
|
13096
|
+
if (segments[0] !== "webhooks") segments.unshift("webhooks");
|
|
13097
|
+
routes.add(`/api/${specId}/${segments.join("/")}`);
|
|
13098
|
+
}
|
|
13099
|
+
}
|
|
13100
|
+
return [...routes];
|
|
13101
|
+
}
|
|
12669
13102
|
function servedOperations(source, parsed) {
|
|
12670
13103
|
let operations = parsed.get(source.filename);
|
|
12671
13104
|
if (!operations) {
|
|
@@ -13379,10 +13812,13 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
|
|
|
13379
13812
|
if (!pathname.startsWith("/") || pathname.startsWith("//") || pathname.includes("\\")) return target2;
|
|
13380
13813
|
const segments = pathname.slice(1).split("/");
|
|
13381
13814
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return target2;
|
|
13382
|
-
|
|
13815
|
+
const markdown = pathname.endsWith(".md");
|
|
13816
|
+
const sourceMdx = pathname.endsWith(".mdx");
|
|
13817
|
+
const routeSegments = markdown || sourceMdx ? [...segments.slice(0, -1), segments.at(-1).replace(/\.mdx?$/, "")] : segments;
|
|
13818
|
+
if (pageIds.has(routeSegments.join("/"))) return sourceMdx ? `/${routeSegments.join("/")}${suffix}` : target2;
|
|
13383
13819
|
for (let count = 1; count < segments.length && count <= 3; count++) {
|
|
13384
|
-
const candidate =
|
|
13385
|
-
if (pageIds.has(candidate)) return `/${candidate}${suffix}`;
|
|
13820
|
+
const candidate = routeSegments.slice(count).join("/");
|
|
13821
|
+
if (pageIds.has(candidate)) return `/${candidate}${markdown ? ".md" : ""}${suffix}`;
|
|
13386
13822
|
}
|
|
13387
13823
|
return target2;
|
|
13388
13824
|
};
|
|
@@ -13462,11 +13898,11 @@ function preserveLinkedAnchors(pages) {
|
|
|
13462
13898
|
const headingSlugs = /* @__PURE__ */ new Map();
|
|
13463
13899
|
const slugCounts = /* @__PURE__ */ new Map();
|
|
13464
13900
|
for (const heading of headings) {
|
|
13465
|
-
const
|
|
13466
|
-
existing.add(
|
|
13467
|
-
const seen = slugCounts.get(
|
|
13468
|
-
slugCounts.set(
|
|
13469
|
-
if (
|
|
13901
|
+
const slug3 = heading.text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
13902
|
+
existing.add(slug3);
|
|
13903
|
+
const seen = slugCounts.get(slug3) ?? 0;
|
|
13904
|
+
slugCounts.set(slug3, seen + 1);
|
|
13905
|
+
if (slug3) headingSlugs.set(seen === 0 ? slug3 : `${slug3}-${seen}`, heading.index);
|
|
13470
13906
|
}
|
|
13471
13907
|
const headingAliases = /* @__PURE__ */ new Map();
|
|
13472
13908
|
const tableAliases = /* @__PURE__ */ new Map();
|
|
@@ -13531,9 +13967,9 @@ function addDatedHeadingAliases(body, date) {
|
|
|
13531
13967
|
if (open) return [line];
|
|
13532
13968
|
const heading = line.match(/^#{2,6}\s+(.+?)\s*#*\s*$/);
|
|
13533
13969
|
if (!heading) return [line];
|
|
13534
|
-
const
|
|
13535
|
-
const id = `${date}-${
|
|
13536
|
-
if (!
|
|
13970
|
+
const slug3 = heading[1].replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_]/g, "").normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").trim().replace(/\s+/g, "-");
|
|
13971
|
+
const id = `${date}-${slug3}`;
|
|
13972
|
+
if (!slug3 || existingIds.has(id)) return [line];
|
|
13537
13973
|
existingIds.add(id);
|
|
13538
13974
|
return [`<a id="${id}" />`, "", line];
|
|
13539
13975
|
}).join("\n");
|
|
@@ -14751,9 +15187,9 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
14751
15187
|
// hierarchy (never just its own segment) and wins over a docs.yml slug.
|
|
14752
15188
|
...platform === "fern" ? {
|
|
14753
15189
|
resolveIdentity: (frontmatter, fallback) => {
|
|
14754
|
-
const
|
|
14755
|
-
if (!
|
|
14756
|
-
const overridden = pageIdFromReference(
|
|
15190
|
+
const slug3 = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
15191
|
+
if (!slug3) return fallback;
|
|
15192
|
+
const overridden = pageIdFromReference(slug3, true);
|
|
14757
15193
|
if (!overridden) return fallback;
|
|
14758
15194
|
fernIdRenames.set(fallback.navigationId, overridden);
|
|
14759
15195
|
return { ...fallback, id: overridden, navigationId: overridden };
|
|
@@ -15135,7 +15571,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
15135
15571
|
message: `Skipped ${rootStyleProjection.omittedSelectors} stylesheet selector${rootStyleProjection.omittedSelectors === 1 ? "" : "s"} that target platform-owned or unreferenced markup. Recreate any intentional shell customization with Thally configuration.`
|
|
15136
15572
|
});
|
|
15137
15573
|
if (rootStyleProjection.css.trim()) {
|
|
15138
|
-
const identity =
|
|
15574
|
+
const identity = createHash5("sha256").update(componentSourceIdentity(options.sourceUrl, repositoryDir, mintlifyProjectRoot)).digest("hex").slice(0, 12);
|
|
15139
15575
|
const assetPath = `migrated/${identity}/style.css`;
|
|
15140
15576
|
const content = Buffer.from(rootStyleProjection.css);
|
|
15141
15577
|
assets.push({ path: assetPath, content });
|
|
@@ -16022,6 +16458,32 @@ function renderPage(bundle, page) {
|
|
|
16022
16458
|
function isIntegrationsObject(value) {
|
|
16023
16459
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16024
16460
|
}
|
|
16461
|
+
function mergeRedirects(existing = [], incoming = []) {
|
|
16462
|
+
const redirects = [...existing];
|
|
16463
|
+
const key = (source) => literalRoute(source) ?? trimTrailingSlashes2(source).toLowerCase();
|
|
16464
|
+
const destinationKey = (destination) => literalRoute(destinationRoute(destination) ?? "");
|
|
16465
|
+
const sources = new Set(existing.map((redirect) => key(redirect.source)));
|
|
16466
|
+
for (const redirect of incoming) {
|
|
16467
|
+
const source = key(redirect.source);
|
|
16468
|
+
if (sources.has(source)) continue;
|
|
16469
|
+
const visited = /* @__PURE__ */ new Set([source]);
|
|
16470
|
+
let destination = destinationKey(redirect.destination);
|
|
16471
|
+
let cycle = false;
|
|
16472
|
+
while (destination) {
|
|
16473
|
+
if (visited.has(destination)) {
|
|
16474
|
+
cycle = true;
|
|
16475
|
+
break;
|
|
16476
|
+
}
|
|
16477
|
+
visited.add(destination);
|
|
16478
|
+
const next = redirects.find((candidate) => literalRoute(candidate.source) === destination);
|
|
16479
|
+
destination = next ? destinationKey(next.destination) : null;
|
|
16480
|
+
}
|
|
16481
|
+
if (cycle) continue;
|
|
16482
|
+
redirects.push(redirect);
|
|
16483
|
+
sources.add(source);
|
|
16484
|
+
}
|
|
16485
|
+
return redirects;
|
|
16486
|
+
}
|
|
16025
16487
|
function mergeMigrationConfig(existing, incoming) {
|
|
16026
16488
|
const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
|
|
16027
16489
|
const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
|
|
@@ -16089,6 +16551,10 @@ function mergeMigrationConfig(existing, incoming) {
|
|
|
16089
16551
|
...incoming,
|
|
16090
16552
|
...existing,
|
|
16091
16553
|
tabs,
|
|
16554
|
+
...existing.redirects || incoming.redirects ? { redirects: mergeRedirects(existing.redirects, incoming.redirects) } : {},
|
|
16555
|
+
// A starter's primary action must not discard a newly imported logo.
|
|
16556
|
+
// Owner-authored fields, including an explicit null logo, remain authoritative.
|
|
16557
|
+
...incoming.navbar || existing.navbar ? { navbar: { ...incoming.navbar, ...existing.navbar } } : {},
|
|
16092
16558
|
// Collection presentation belongs to the imported information
|
|
16093
16559
|
// architecture; starter defaults must not coerce source dropdowns to tabs.
|
|
16094
16560
|
navigation: incoming.navigation ?? existing.navigation,
|
|
@@ -16120,12 +16586,12 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
16120
16586
|
}
|
|
16121
16587
|
|
|
16122
16588
|
// src/url-run.ts
|
|
16123
|
-
import { createHash as
|
|
16589
|
+
import { createHash as createHash7 } from "crypto";
|
|
16124
16590
|
import { load as load8 } from "cheerio";
|
|
16125
16591
|
import { parse as parse2, postprocess, preprocess } from "micromark";
|
|
16126
|
-
import
|
|
16592
|
+
import remarkParse6 from "remark-parse";
|
|
16127
16593
|
import remarkMdx5 from "remark-mdx";
|
|
16128
|
-
import { unified as
|
|
16594
|
+
import { unified as unified6 } from "unified";
|
|
16129
16595
|
|
|
16130
16596
|
// src/url-asset-cache.ts
|
|
16131
16597
|
function createMigrationAssetCache() {
|
|
@@ -16656,7 +17122,7 @@ function mergeSourceNavigation(existing, incoming) {
|
|
|
16656
17122
|
}
|
|
16657
17123
|
|
|
16658
17124
|
// src/url-identity.ts
|
|
16659
|
-
import { createHash as
|
|
17125
|
+
import { createHash as createHash6 } from "crypto";
|
|
16660
17126
|
import { load as load7 } from "cheerio";
|
|
16661
17127
|
var directory = (path) => path.replace(/\/index\/?$/i, "").replace(/\/$/, "") || "/";
|
|
16662
17128
|
function migrationSectionQueryCandidate(source, anchors) {
|
|
@@ -16717,7 +17183,7 @@ function observedMigrationArticleIdentity(document) {
|
|
|
16717
17183
|
canonical2.pathname = canonical2.pathname.replace(/\/+$/, "") || "/";
|
|
16718
17184
|
const removed = [...source.searchParams].filter(([key, value]) => !canonical2.searchParams.getAll(key).includes(value));
|
|
16719
17185
|
const sectionAnchor = removed.length === 1 && anchors.includes(removed[0][1]) ? removed[0][1] : void 0;
|
|
16720
|
-
return { canonicalUrl: canonical2.href, articleSha256:
|
|
17186
|
+
return { canonicalUrl: canonical2.href, articleSha256: createHash6("sha256").update(payload).digest("hex"), ...sectionAnchor ? { sectionAnchor } : {} };
|
|
16721
17187
|
}
|
|
16722
17188
|
|
|
16723
17189
|
// src/url-api-schema-controls.ts
|
|
@@ -17044,8 +17510,8 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17044
17510
|
const metadata = object2(resolveValue(value));
|
|
17045
17511
|
const declaration = resolveValue(metadata?.openapi);
|
|
17046
17512
|
if (typeof declaration !== "string") return false;
|
|
17047
|
-
const
|
|
17048
|
-
return
|
|
17513
|
+
const tokens2 = declaration.trim().split(/\s+/);
|
|
17514
|
+
return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && tokens2.at(-1) === operation.path;
|
|
17049
17515
|
};
|
|
17050
17516
|
const active = /* @__PURE__ */ new Set();
|
|
17051
17517
|
const visit = (input, provider, group, depth, owners = []) => {
|
|
@@ -17091,9 +17557,9 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17091
17557
|
const hint = object2(resolveValue(scope?.pageMetadata)), metadata = object2(resolveValue(props.pageMetadata));
|
|
17092
17558
|
const declaration = resolveValue(hint?.openapi);
|
|
17093
17559
|
if (typeof declaration !== "string" || typeof hint?.href !== "string" || !hint.href || hint.href !== metadata?.href) return false;
|
|
17094
|
-
const
|
|
17560
|
+
const tokens2 = declaration.trim().split(/\s+/);
|
|
17095
17561
|
const template = (path) => path.split("/").map((segment) => /^\{[^{}\/]+\}$/.test(segment) ? "{}" : segment).join("/");
|
|
17096
|
-
return
|
|
17562
|
+
return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && template(tokens2.at(-1) ?? "") === template(operation.path);
|
|
17097
17563
|
})()) generatedControllers += 1;
|
|
17098
17564
|
}
|
|
17099
17565
|
let local = group;
|
|
@@ -17202,14 +17668,14 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17202
17668
|
|
|
17203
17669
|
// src/url-run.ts
|
|
17204
17670
|
var CONVERTER_VERSION = "url-evidence-v7";
|
|
17205
|
-
var parser3 =
|
|
17671
|
+
var parser3 = unified6().use(remarkParse6).use(remarkMdx5);
|
|
17206
17672
|
var ACCEPT = "text/html,application/xhtml+xml";
|
|
17207
17673
|
var MAX_RESPONSE_BYTES = 8e6;
|
|
17208
17674
|
var MAX_ASSET_BYTES2 = 1e7;
|
|
17209
17675
|
var MAX_RECORDS = 1e5;
|
|
17210
17676
|
var LOCALES = /* @__PURE__ */ new Set(["ar", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hi", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "pt", "pt-br", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi", "zh", "zh-cn", "zh-hans", "zh-hant"]);
|
|
17211
17677
|
function hash2(value) {
|
|
17212
|
-
return
|
|
17678
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
17213
17679
|
}
|
|
17214
17680
|
function canonicalJson(value) {
|
|
17215
17681
|
const canonical2 = (value2) => Array.isArray(value2) ? value2.map(canonical2) : value2 !== null && typeof value2 === "object" ? Object.fromEntries(Object.entries(value2).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, field]) => [key, canonical2(field)])) : value2;
|
|
@@ -18474,133 +18940,8 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
18474
18940
|
}
|
|
18475
18941
|
|
|
18476
18942
|
// src/remote-api.ts
|
|
18477
|
-
import { createHash as
|
|
18478
|
-
|
|
18479
|
-
import { request } from "https";
|
|
18480
|
-
import { isIP } from "net";
|
|
18481
|
-
var MAX_SPEC_BYTES2 = 25e6;
|
|
18482
|
-
var MAX_REDIRECTS = 3;
|
|
18483
|
-
function isPublicAddress(address, family) {
|
|
18484
|
-
if (family === 4) {
|
|
18485
|
-
const parts = address.split(".").map(Number);
|
|
18486
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
18487
|
-
const [a, b] = parts;
|
|
18488
|
-
return !(a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && (b === 0 || b === 168) || a === 198 && (b === 18 || b === 19) || a === 203 && b === 0 || a === 198 && b === 51);
|
|
18489
|
-
}
|
|
18490
|
-
return family === 6 && /^[23][0-9a-f]{0,3}:/i.test(address) && !/^2001:db8:/i.test(address);
|
|
18491
|
-
}
|
|
18492
|
-
function validatedSpecUrl(value) {
|
|
18493
|
-
const url = new URL(value);
|
|
18494
|
-
if (url.protocol !== "https:" || url.username || url.password || isIP(url.hostname)) {
|
|
18495
|
-
throw new Error("Remote OpenAPI references must use an HTTPS domain without credentials or an IP literal.");
|
|
18496
|
-
}
|
|
18497
|
-
url.hash = "";
|
|
18498
|
-
return url;
|
|
18499
|
-
}
|
|
18500
|
-
async function getPinnedAddress(url) {
|
|
18501
|
-
const addresses = await lookup(url.hostname, { all: true });
|
|
18502
|
-
if (addresses.length === 0 || addresses.some((candidate) => !isPublicAddress(candidate.address, candidate.family))) {
|
|
18503
|
-
throw new Error("The OpenAPI host did not resolve exclusively to public addresses.");
|
|
18504
|
-
}
|
|
18505
|
-
return addresses[0];
|
|
18506
|
-
}
|
|
18507
|
-
async function requestPinnedSpec(url) {
|
|
18508
|
-
const pinned = await getPinnedAddress(url);
|
|
18509
|
-
return new Promise((resolve5, reject) => {
|
|
18510
|
-
const req = request(url, {
|
|
18511
|
-
headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
|
|
18512
|
-
signal: AbortSignal.timeout(3e4),
|
|
18513
|
-
// Node's dual-stack connector asks lookup for `all: true`; that
|
|
18514
|
-
// callback must receive address records, not a single address string.
|
|
18515
|
-
lookup: (_hostname, options, callback) => {
|
|
18516
|
-
if (typeof options === "object" && options.all) callback(null, [pinned]);
|
|
18517
|
-
else callback(null, pinned.address, pinned.family);
|
|
18518
|
-
}
|
|
18519
|
-
}, (response) => {
|
|
18520
|
-
const status = response.statusCode ?? 0;
|
|
18521
|
-
if (status >= 300 && status < 400) {
|
|
18522
|
-
response.resume();
|
|
18523
|
-
resolve5({ status, location: response.headers.location, body: Buffer.alloc(0) });
|
|
18524
|
-
return;
|
|
18525
|
-
}
|
|
18526
|
-
if (status < 200 || status >= 300) {
|
|
18527
|
-
response.resume();
|
|
18528
|
-
reject(new Error(`OpenAPI server returned ${status}.`));
|
|
18529
|
-
return;
|
|
18530
|
-
}
|
|
18531
|
-
const statedBytes = Number(response.headers["content-length"] ?? 0);
|
|
18532
|
-
if (statedBytes > MAX_SPEC_BYTES2) {
|
|
18533
|
-
response.destroy();
|
|
18534
|
-
reject(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
18535
|
-
return;
|
|
18536
|
-
}
|
|
18537
|
-
const chunks = [];
|
|
18538
|
-
let bytes = 0;
|
|
18539
|
-
response.on("data", (chunk) => {
|
|
18540
|
-
bytes += chunk.length;
|
|
18541
|
-
if (bytes > MAX_SPEC_BYTES2) {
|
|
18542
|
-
response.destroy(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
18543
|
-
return;
|
|
18544
|
-
}
|
|
18545
|
-
chunks.push(chunk);
|
|
18546
|
-
});
|
|
18547
|
-
response.on("end", () => resolve5({ status, body: Buffer.concat(chunks) }));
|
|
18548
|
-
response.on("error", reject);
|
|
18549
|
-
});
|
|
18550
|
-
req.on("error", reject);
|
|
18551
|
-
req.end();
|
|
18552
|
-
});
|
|
18553
|
-
}
|
|
18554
|
-
async function downloadSpec(value, fetcher) {
|
|
18555
|
-
let url = validatedSpecUrl(value);
|
|
18556
|
-
if (fetcher) {
|
|
18557
|
-
const response = await fetcher(url, { accept: "application/json, application/yaml, text/yaml" });
|
|
18558
|
-
validatedSpecUrl(response.finalUrl.toString());
|
|
18559
|
-
const body = Buffer.from(response.body);
|
|
18560
|
-
if (body.length > MAX_SPEC_BYTES2) throw new Error("Remote OpenAPI spec exceeds the 25 MB import limit.");
|
|
18561
|
-
return body;
|
|
18562
|
-
}
|
|
18563
|
-
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
18564
|
-
const response = await requestPinnedSpec(url);
|
|
18565
|
-
if (response.status >= 300 && response.status < 400) {
|
|
18566
|
-
if (!response.location || hop === MAX_REDIRECTS) throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
18567
|
-
url = validatedSpecUrl(new URL(response.location, url).toString());
|
|
18568
|
-
continue;
|
|
18569
|
-
}
|
|
18570
|
-
return response.body;
|
|
18571
|
-
}
|
|
18572
|
-
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
18573
|
-
}
|
|
18574
|
-
function parseOpenApi(parsed) {
|
|
18575
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
18576
|
-
const document = parsed;
|
|
18577
|
-
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
18578
|
-
throw new Error("Remote specification is not OpenAPI 3.x.");
|
|
18579
|
-
}
|
|
18580
|
-
if (!document.paths || typeof document.paths !== "object" || Array.isArray(document.paths)) {
|
|
18581
|
-
throw new Error("Remote OpenAPI spec has no paths object.");
|
|
18582
|
-
}
|
|
18583
|
-
const paths = Object.entries(document.paths);
|
|
18584
|
-
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
18585
|
-
const operations = [];
|
|
18586
|
-
for (const [path, value] of paths) {
|
|
18587
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || isWithheld(value)) continue;
|
|
18588
|
-
for (const [method, raw] of Object.entries(value)) {
|
|
18589
|
-
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
18590
|
-
const operation = raw;
|
|
18591
|
-
if (isWithheld(operation)) continue;
|
|
18592
|
-
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
18593
|
-
operations.push({
|
|
18594
|
-
method: method.toLowerCase(),
|
|
18595
|
-
path,
|
|
18596
|
-
tag: typeof tags[0] === "string" ? tags[0] : "API Reference",
|
|
18597
|
-
summary: typeof operation.summary === "string" ? operation.summary : String(operation.operationId ?? "")
|
|
18598
|
-
});
|
|
18599
|
-
}
|
|
18600
|
-
}
|
|
18601
|
-
return operations;
|
|
18602
|
-
}
|
|
18603
|
-
function slug(value) {
|
|
18943
|
+
import { createHash as createHash8 } from "crypto";
|
|
18944
|
+
function slug2(value) {
|
|
18604
18945
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
18605
18946
|
}
|
|
18606
18947
|
function operationTarget(specId, operation) {
|
|
@@ -18649,7 +18990,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
18649
18990
|
}
|
|
18650
18991
|
const { format: extension, document } = parseSpecDocument(body.toString("utf8"));
|
|
18651
18992
|
const operations = parseOpenApi(document);
|
|
18652
|
-
const filename = `openapi-${
|
|
18993
|
+
const filename = `openapi-${createHash8("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
18653
18994
|
if (!assets.some((asset) => asset.path === `openapi/${filename}`)) assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
18654
18995
|
for (const page of pages) {
|
|
18655
18996
|
const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
|
|
@@ -18734,7 +19075,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
18734
19075
|
const listed = reference.directories?.length && reference.directories.every((directory2) => directory2.operations) ? new Set(reference.directories.flatMap((directory2) => directory2.operations ?? []).map((name) => name.trim().split(/\s+/)).map(([method, ...path]) => `${method.toLowerCase()} ${path.join(" ")}`)) : null;
|
|
18735
19076
|
for (const operation of operations) {
|
|
18736
19077
|
if (listed && !listed.has(`${operation.method} ${operation.path}`)) continue;
|
|
18737
|
-
const key = `${
|
|
19078
|
+
const key = `${slug2(operation.tag)}/${slug2(operation.summary)}`;
|
|
18738
19079
|
guesses.set(key, guesses.has(key) || !specId ? null : operationTarget(specId, operation));
|
|
18739
19080
|
}
|
|
18740
19081
|
}
|
|
@@ -18777,12 +19118,16 @@ export {
|
|
|
18777
19118
|
detectRepositoryPlatform,
|
|
18778
19119
|
gitmodulePaths,
|
|
18779
19120
|
hydrateRemoteApiSpecs,
|
|
19121
|
+
hydrateUrlOpenApi,
|
|
18780
19122
|
importSourceRef,
|
|
18781
19123
|
isPassiveSvg,
|
|
19124
|
+
literalRoute,
|
|
18782
19125
|
mergeMigrationConfig,
|
|
18783
19126
|
migrateRepository,
|
|
18784
19127
|
migrateUrl,
|
|
19128
|
+
migrationApiRoutes,
|
|
18785
19129
|
normalizeMdx,
|
|
19130
|
+
pageEntryRoutes,
|
|
18786
19131
|
parseGitHubRepositoryUrl,
|
|
18787
19132
|
parseMarkdownPage,
|
|
18788
19133
|
parseSourceRefFlags,
|