@thallylabs/migrate 0.2.41 → 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 +20 -1
- package/dist/index.d.ts +36 -3
- package/dist/index.js +655 -227
- package/dist/index.js.map +1 -1
- package/dist/url-migration.d.ts +2 -2
- package/dist/url-migration.js +332 -64
- package/dist/url-migration.js.map +1 -1
- package/dist/{url-run-mSKFqHx_.d.ts → url-run-CPzQfjRW.d.ts} +2 -0
- package/package.json +2 -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}$/;
|
|
@@ -7860,7 +8248,10 @@ var VIDEO_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
|
7860
8248
|
"width",
|
|
7861
8249
|
"height",
|
|
7862
8250
|
"preload",
|
|
7863
|
-
"aria-label"
|
|
8251
|
+
"aria-label",
|
|
8252
|
+
// Publisher asset provenance does not change playback and is never emitted.
|
|
8253
|
+
// Keep this explicit: arbitrary data attributes may drive custom controls.
|
|
8254
|
+
"data-path"
|
|
7864
8255
|
]);
|
|
7865
8256
|
var YOUTUBE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
7866
8257
|
"src",
|
|
@@ -7925,7 +8316,7 @@ function projectMigrationMedia(html, base) {
|
|
|
7925
8316
|
}
|
|
7926
8317
|
|
|
7927
8318
|
// src/url-static-content.ts
|
|
7928
|
-
import { createHash as
|
|
8319
|
+
import { createHash as createHash3 } from "crypto";
|
|
7929
8320
|
import { load as load3 } from "cheerio";
|
|
7930
8321
|
|
|
7931
8322
|
// src/svg-policy.ts
|
|
@@ -8247,7 +8638,7 @@ function projectMigrationSvg(html) {
|
|
|
8247
8638
|
if (svg.attr("xmlns") === void 0) svg.attr("xmlns", "http://www.w3.org/2000/svg");
|
|
8248
8639
|
const content = Buffer.from($.xml(svg));
|
|
8249
8640
|
if (!isPassiveSvg(content)) return null;
|
|
8250
|
-
const sha256 =
|
|
8641
|
+
const sha256 = createHash3("sha256").update(content).digest("hex");
|
|
8251
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);
|
|
8252
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();
|
|
8253
8644
|
return { asset: { path: `migration-assets/${sha256}.svg`, content }, alternative };
|
|
@@ -8361,12 +8752,12 @@ function projectCapturedTabs($, root) {
|
|
|
8361
8752
|
}
|
|
8362
8753
|
|
|
8363
8754
|
// src/url-flight-content.ts
|
|
8364
|
-
import { createHash as
|
|
8755
|
+
import { createHash as createHash4 } from "crypto";
|
|
8365
8756
|
import remarkMdx3 from "remark-mdx";
|
|
8366
|
-
import
|
|
8757
|
+
import remarkParse4 from "remark-parse";
|
|
8367
8758
|
import remarkGfm2 from "remark-gfm";
|
|
8368
|
-
import { unified as
|
|
8369
|
-
var parser2 =
|
|
8759
|
+
import { unified as unified4 } from "unified";
|
|
8760
|
+
var parser2 = unified4().use(remarkParse4).use(remarkGfm2).use(remarkMdx3);
|
|
8370
8761
|
var MAX_SOURCE_BYTES = 8e6;
|
|
8371
8762
|
var sourceTrees = /* @__PURE__ */ new Map();
|
|
8372
8763
|
var cachedSourceBytes = 0;
|
|
@@ -8538,7 +8929,7 @@ function sourceText(node) {
|
|
|
8538
8929
|
return node.value ?? (node.children ?? []).map(sourceText).join("");
|
|
8539
8930
|
}
|
|
8540
8931
|
function sourceNodes(source) {
|
|
8541
|
-
const key =
|
|
8932
|
+
const key = createHash4("sha256").update(source).digest("hex");
|
|
8542
8933
|
const cached = sourceTrees.get(key);
|
|
8543
8934
|
if (cached) {
|
|
8544
8935
|
sourceTrees.delete(key);
|
|
@@ -8887,7 +9278,7 @@ function recoverFlightDisclosures($, root, document, article, projectionBudget)
|
|
|
8887
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;
|
|
8888
9279
|
let html;
|
|
8889
9280
|
if (!panel.text().trim() && !panel.children().length) {
|
|
8890
|
-
const prefix = `thally-flight-${
|
|
9281
|
+
const prefix = `thally-flight-${createHash4("sha256").update(target2).digest("hex").slice(0, 16)}`;
|
|
8891
9282
|
const rendered = flightStaticHtml(document, source.props.children, prefix, projectionBudget);
|
|
8892
9283
|
if (rendered === null || !rendered.trim()) continue;
|
|
8893
9284
|
html = rendered;
|
|
@@ -9126,8 +9517,13 @@ function recoverFlightArticleCode($, root) {
|
|
|
9126
9517
|
|
|
9127
9518
|
// src/redirects.ts
|
|
9128
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
|
+
}
|
|
9129
9525
|
function exactRoute(route) {
|
|
9130
|
-
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route
|
|
9526
|
+
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? trimTrailingSlashes2(route) || "/" : null;
|
|
9131
9527
|
}
|
|
9132
9528
|
function literalRoute(route) {
|
|
9133
9529
|
return exactRoute(route)?.toLowerCase() ?? null;
|
|
@@ -9390,8 +9786,8 @@ var MINTLIFY_ARTICLE_LABELS = [
|
|
|
9390
9786
|
{ baseUrl: "Select base URL", permalink: "Navigate to header" },
|
|
9391
9787
|
{ baseUrl: "\u9009\u62E9\u57FA\u7840URL", permalink: "\u5BFC\u822A\u5230\u6807\u9898" }
|
|
9392
9788
|
];
|
|
9393
|
-
var remoteMarkdownParser =
|
|
9394
|
-
var embeddedOpenApiParser =
|
|
9789
|
+
var remoteMarkdownParser = unified5().use(remarkParse5).use(remarkMdx4);
|
|
9790
|
+
var embeddedOpenApiParser = unified5().use(remarkParse5);
|
|
9395
9791
|
function validateMigrationUrl(value) {
|
|
9396
9792
|
let url;
|
|
9397
9793
|
try {
|
|
@@ -10007,9 +10403,13 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
|
|
|
10007
10403
|
return `/${target2.search}${target2.hash}`;
|
|
10008
10404
|
}
|
|
10009
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}`;
|
|
10010
10409
|
if (scopePath !== "/" && (path === scopePath || path.startsWith(`${scopePath}/`))) {
|
|
10011
10410
|
path = path.slice(scopePath.length);
|
|
10012
10411
|
}
|
|
10412
|
+
if (["/llms.txt", "/llms-full.txt", "/openapi.json", "/openapi.yaml", "/robots.txt", "/sitemap.xml"].includes(path)) return `${path}${target2.search}${target2.hash}`;
|
|
10013
10413
|
const id = pageIdFromReference(path.replace(/^\/+/, "") || "introduction");
|
|
10014
10414
|
const localizedId = id ? canonicalLocalizedPageId(id) : id;
|
|
10015
10415
|
const destinationId = localizedId === sourceHomePageId ? "introduction" : localizedId;
|
|
@@ -10019,7 +10419,7 @@ function migratedHref(href, currentUrl, source, scopePath, importedIds, sourceHo
|
|
|
10019
10419
|
}
|
|
10020
10420
|
if (importedDestinationId) {
|
|
10021
10421
|
const destination = importedDestinationId === "introduction" ? "/" : `/${importedDestinationId}`;
|
|
10022
|
-
return `${destination}${target2.search}${target2.hash}`;
|
|
10422
|
+
return `${destination === "/" && isMarkdown ? "/introduction" : destination}${isMarkdown ? ".md" : ""}${target2.search}${target2.hash}`;
|
|
10023
10423
|
}
|
|
10024
10424
|
return target2.toString();
|
|
10025
10425
|
} catch {
|
|
@@ -11194,9 +11594,9 @@ async function migrateUrl(options) {
|
|
|
11194
11594
|
]
|
|
11195
11595
|
};
|
|
11196
11596
|
}
|
|
11197
|
-
if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
|
|
11597
|
+
if (platform === "mintlify") docsConfig = { ...addMintlifyDirectoryRedirects(docsConfig, pages), markdown: { enabled: true } };
|
|
11198
11598
|
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), pages, warnings);
|
|
11199
|
-
|
|
11599
|
+
const bundle = {
|
|
11200
11600
|
sourceUrl: source.toString(),
|
|
11201
11601
|
sourceKind: "url",
|
|
11202
11602
|
platform,
|
|
@@ -11216,6 +11616,20 @@ async function migrateUrl(options) {
|
|
|
11216
11616
|
skipped: Math.max(0, visited.size - pages.length)
|
|
11217
11617
|
}
|
|
11218
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;
|
|
11219
11633
|
}
|
|
11220
11634
|
|
|
11221
11635
|
// src/source-refs.ts
|
|
@@ -12367,12 +12781,12 @@ function classifyApiSpec(path) {
|
|
|
12367
12781
|
return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
|
|
12368
12782
|
}
|
|
12369
12783
|
var SPEC_DIRECTORY = "openapi";
|
|
12370
|
-
var
|
|
12784
|
+
var MAX_SPEC_BYTES2 = 5e7;
|
|
12371
12785
|
function readSpecFile(absolutePath, relativePath, warnings) {
|
|
12372
|
-
if (lstatSync6(absolutePath).size >
|
|
12786
|
+
if (lstatSync6(absolutePath).size > MAX_SPEC_BYTES2) {
|
|
12373
12787
|
warnings.push({
|
|
12374
12788
|
code: "skipped-file",
|
|
12375
|
-
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.`,
|
|
12376
12790
|
source: relativePath
|
|
12377
12791
|
});
|
|
12378
12792
|
return void 0;
|
|
@@ -12598,7 +13012,7 @@ function apiTabSpecIds(tabs) {
|
|
|
12598
13012
|
return tabs.filter((tab) => !tab.hidden && tab.api).map((tab, index) => ({ tab, specId: index === 0 ? "default" : slugifyApiSpecId(tab.tab) }));
|
|
12599
13013
|
}
|
|
12600
13014
|
var flagged = (value) => value === true || value === "true";
|
|
12601
|
-
var
|
|
13015
|
+
var isWithheld2 = (node) => flagged(node["x-hidden"]) || flagged(node["x-excluded"]);
|
|
12602
13016
|
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12603
13017
|
function resolveLocalSpecRef(document, ref) {
|
|
12604
13018
|
if (ref !== "#" && !ref.startsWith("#/")) return void 0;
|
|
@@ -12625,7 +13039,7 @@ function viewSpecPathItem(document, entry) {
|
|
|
12625
13039
|
}
|
|
12626
13040
|
const item = Object.assign({}, ...[...chain].reverse());
|
|
12627
13041
|
delete item.$ref;
|
|
12628
|
-
return { item, withheld: chain.some(
|
|
13042
|
+
return { item, withheld: chain.some(isWithheld2) };
|
|
12629
13043
|
}
|
|
12630
13044
|
function parseOpenApiOperationsForLinking(content) {
|
|
12631
13045
|
const document = readSpecDocument(content);
|
|
@@ -12649,7 +13063,7 @@ function visibleOperations(document, entries) {
|
|
|
12649
13063
|
const operation = item[method];
|
|
12650
13064
|
if (!operation || typeof operation !== "object") continue;
|
|
12651
13065
|
const op = operation;
|
|
12652
|
-
if (
|
|
13066
|
+
if (isWithheld2(op)) continue;
|
|
12653
13067
|
const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
|
|
12654
13068
|
operations.push({
|
|
12655
13069
|
method,
|
|
@@ -12663,6 +13077,28 @@ function visibleOperations(document, entries) {
|
|
|
12663
13077
|
}
|
|
12664
13078
|
return operations;
|
|
12665
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
|
+
}
|
|
12666
13102
|
function servedOperations(source, parsed) {
|
|
12667
13103
|
let operations = parsed.get(source.filename);
|
|
12668
13104
|
if (!operations) {
|
|
@@ -13376,10 +13812,13 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
|
|
|
13376
13812
|
if (!pathname.startsWith("/") || pathname.startsWith("//") || pathname.includes("\\")) return target2;
|
|
13377
13813
|
const segments = pathname.slice(1).split("/");
|
|
13378
13814
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return target2;
|
|
13379
|
-
|
|
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;
|
|
13380
13819
|
for (let count = 1; count < segments.length && count <= 3; count++) {
|
|
13381
|
-
const candidate =
|
|
13382
|
-
if (pageIds.has(candidate)) return `/${candidate}${suffix}`;
|
|
13820
|
+
const candidate = routeSegments.slice(count).join("/");
|
|
13821
|
+
if (pageIds.has(candidate)) return `/${candidate}${markdown ? ".md" : ""}${suffix}`;
|
|
13383
13822
|
}
|
|
13384
13823
|
return target2;
|
|
13385
13824
|
};
|
|
@@ -13459,11 +13898,11 @@ function preserveLinkedAnchors(pages) {
|
|
|
13459
13898
|
const headingSlugs = /* @__PURE__ */ new Map();
|
|
13460
13899
|
const slugCounts = /* @__PURE__ */ new Map();
|
|
13461
13900
|
for (const heading of headings) {
|
|
13462
|
-
const
|
|
13463
|
-
existing.add(
|
|
13464
|
-
const seen = slugCounts.get(
|
|
13465
|
-
slugCounts.set(
|
|
13466
|
-
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);
|
|
13467
13906
|
}
|
|
13468
13907
|
const headingAliases = /* @__PURE__ */ new Map();
|
|
13469
13908
|
const tableAliases = /* @__PURE__ */ new Map();
|
|
@@ -13528,9 +13967,9 @@ function addDatedHeadingAliases(body, date) {
|
|
|
13528
13967
|
if (open) return [line];
|
|
13529
13968
|
const heading = line.match(/^#{2,6}\s+(.+?)\s*#*\s*$/);
|
|
13530
13969
|
if (!heading) return [line];
|
|
13531
|
-
const
|
|
13532
|
-
const id = `${date}-${
|
|
13533
|
-
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];
|
|
13534
13973
|
existingIds.add(id);
|
|
13535
13974
|
return [`<a id="${id}" />`, "", line];
|
|
13536
13975
|
}).join("\n");
|
|
@@ -14748,9 +15187,9 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
14748
15187
|
// hierarchy (never just its own segment) and wins over a docs.yml slug.
|
|
14749
15188
|
...platform === "fern" ? {
|
|
14750
15189
|
resolveIdentity: (frontmatter, fallback) => {
|
|
14751
|
-
const
|
|
14752
|
-
if (!
|
|
14753
|
-
const overridden = pageIdFromReference(
|
|
15190
|
+
const slug3 = typeof frontmatter.slug === "string" ? frontmatter.slug.trim() : "";
|
|
15191
|
+
if (!slug3) return fallback;
|
|
15192
|
+
const overridden = pageIdFromReference(slug3, true);
|
|
14754
15193
|
if (!overridden) return fallback;
|
|
14755
15194
|
fernIdRenames.set(fallback.navigationId, overridden);
|
|
14756
15195
|
return { ...fallback, id: overridden, navigationId: overridden };
|
|
@@ -15132,7 +15571,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
15132
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.`
|
|
15133
15572
|
});
|
|
15134
15573
|
if (rootStyleProjection.css.trim()) {
|
|
15135
|
-
const identity =
|
|
15574
|
+
const identity = createHash5("sha256").update(componentSourceIdentity(options.sourceUrl, repositoryDir, mintlifyProjectRoot)).digest("hex").slice(0, 12);
|
|
15136
15575
|
const assetPath = `migrated/${identity}/style.css`;
|
|
15137
15576
|
const content = Buffer.from(rootStyleProjection.css);
|
|
15138
15577
|
assets.push({ path: assetPath, content });
|
|
@@ -16019,6 +16458,32 @@ function renderPage(bundle, page) {
|
|
|
16019
16458
|
function isIntegrationsObject(value) {
|
|
16020
16459
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16021
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
|
+
}
|
|
16022
16487
|
function mergeMigrationConfig(existing, incoming) {
|
|
16023
16488
|
const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
|
|
16024
16489
|
const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
|
|
@@ -16086,6 +16551,10 @@ function mergeMigrationConfig(existing, incoming) {
|
|
|
16086
16551
|
...incoming,
|
|
16087
16552
|
...existing,
|
|
16088
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 } } : {},
|
|
16089
16558
|
// Collection presentation belongs to the imported information
|
|
16090
16559
|
// architecture; starter defaults must not coerce source dropdowns to tabs.
|
|
16091
16560
|
navigation: incoming.navigation ?? existing.navigation,
|
|
@@ -16117,11 +16586,12 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
16117
16586
|
}
|
|
16118
16587
|
|
|
16119
16588
|
// src/url-run.ts
|
|
16120
|
-
import { createHash as
|
|
16589
|
+
import { createHash as createHash7 } from "crypto";
|
|
16121
16590
|
import { load as load8 } from "cheerio";
|
|
16122
|
-
import
|
|
16591
|
+
import { parse as parse2, postprocess, preprocess } from "micromark";
|
|
16592
|
+
import remarkParse6 from "remark-parse";
|
|
16123
16593
|
import remarkMdx5 from "remark-mdx";
|
|
16124
|
-
import { unified as
|
|
16594
|
+
import { unified as unified6 } from "unified";
|
|
16125
16595
|
|
|
16126
16596
|
// src/url-asset-cache.ts
|
|
16127
16597
|
function createMigrationAssetCache() {
|
|
@@ -16652,7 +17122,7 @@ function mergeSourceNavigation(existing, incoming) {
|
|
|
16652
17122
|
}
|
|
16653
17123
|
|
|
16654
17124
|
// src/url-identity.ts
|
|
16655
|
-
import { createHash as
|
|
17125
|
+
import { createHash as createHash6 } from "crypto";
|
|
16656
17126
|
import { load as load7 } from "cheerio";
|
|
16657
17127
|
var directory = (path) => path.replace(/\/index\/?$/i, "").replace(/\/$/, "") || "/";
|
|
16658
17128
|
function migrationSectionQueryCandidate(source, anchors) {
|
|
@@ -16713,7 +17183,7 @@ function observedMigrationArticleIdentity(document) {
|
|
|
16713
17183
|
canonical2.pathname = canonical2.pathname.replace(/\/+$/, "") || "/";
|
|
16714
17184
|
const removed = [...source.searchParams].filter(([key, value]) => !canonical2.searchParams.getAll(key).includes(value));
|
|
16715
17185
|
const sectionAnchor = removed.length === 1 && anchors.includes(removed[0][1]) ? removed[0][1] : void 0;
|
|
16716
|
-
return { canonicalUrl: canonical2.href, articleSha256:
|
|
17186
|
+
return { canonicalUrl: canonical2.href, articleSha256: createHash6("sha256").update(payload).digest("hex"), ...sectionAnchor ? { sectionAnchor } : {} };
|
|
16717
17187
|
}
|
|
16718
17188
|
|
|
16719
17189
|
// src/url-api-schema-controls.ts
|
|
@@ -17040,8 +17510,8 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17040
17510
|
const metadata = object2(resolveValue(value));
|
|
17041
17511
|
const declaration = resolveValue(metadata?.openapi);
|
|
17042
17512
|
if (typeof declaration !== "string") return false;
|
|
17043
|
-
const
|
|
17044
|
-
return
|
|
17513
|
+
const tokens2 = declaration.trim().split(/\s+/);
|
|
17514
|
+
return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && tokens2.at(-1) === operation.path;
|
|
17045
17515
|
};
|
|
17046
17516
|
const active = /* @__PURE__ */ new Set();
|
|
17047
17517
|
const visit = (input, provider, group, depth, owners = []) => {
|
|
@@ -17087,9 +17557,9 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17087
17557
|
const hint = object2(resolveValue(scope?.pageMetadata)), metadata = object2(resolveValue(props.pageMetadata));
|
|
17088
17558
|
const declaration = resolveValue(hint?.openapi);
|
|
17089
17559
|
if (typeof declaration !== "string" || typeof hint?.href !== "string" || !hint.href || hint.href !== metadata?.href) return false;
|
|
17090
|
-
const
|
|
17560
|
+
const tokens2 = declaration.trim().split(/\s+/);
|
|
17091
17561
|
const template = (path) => path.split("/").map((segment) => /^\{[^{}\/]+\}$/.test(segment) ? "{}" : segment).join("/");
|
|
17092
|
-
return
|
|
17562
|
+
return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && template(tokens2.at(-1) ?? "") === template(operation.path);
|
|
17093
17563
|
})()) generatedControllers += 1;
|
|
17094
17564
|
}
|
|
17095
17565
|
let local = group;
|
|
@@ -17197,15 +17667,15 @@ function apiOperationCodeControls(body, specification, operation) {
|
|
|
17197
17667
|
}
|
|
17198
17668
|
|
|
17199
17669
|
// src/url-run.ts
|
|
17200
|
-
var CONVERTER_VERSION = "url-evidence-
|
|
17201
|
-
var parser3 =
|
|
17202
|
-
var ACCEPT = "text/
|
|
17670
|
+
var CONVERTER_VERSION = "url-evidence-v7";
|
|
17671
|
+
var parser3 = unified6().use(remarkParse6).use(remarkMdx5);
|
|
17672
|
+
var ACCEPT = "text/html,application/xhtml+xml";
|
|
17203
17673
|
var MAX_RESPONSE_BYTES = 8e6;
|
|
17204
17674
|
var MAX_ASSET_BYTES2 = 1e7;
|
|
17205
17675
|
var MAX_RECORDS = 1e5;
|
|
17206
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"]);
|
|
17207
17677
|
function hash2(value) {
|
|
17208
|
-
return
|
|
17678
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
17209
17679
|
}
|
|
17210
17680
|
function canonicalJson(value) {
|
|
17211
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;
|
|
@@ -17229,6 +17699,11 @@ function canonical(value, base) {
|
|
|
17229
17699
|
return null;
|
|
17230
17700
|
}
|
|
17231
17701
|
}
|
|
17702
|
+
function sameRouteMarkdownRepresentation(alternate, primary) {
|
|
17703
|
+
const target2 = new URL(alternate), source = new URL(primary);
|
|
17704
|
+
const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
|
|
17705
|
+
return target2.origin === source.origin && target2.search === source.search && directory2(target2.pathname) === directory2(source.pathname) && /\.mdx?$/i.test(target2.pathname);
|
|
17706
|
+
}
|
|
17232
17707
|
function classifyMigrationResponse(document) {
|
|
17233
17708
|
if (document.status && document.status >= 400) return "unsupported";
|
|
17234
17709
|
if (/\/(?:llms(?:-full)?|robots)\.txt$|\/sitemap[^/]*\.xml$|\/search(?:\.html)?$/i.test(document.finalUrl.pathname) || /\.(?:json|ya?ml)$/i.test(document.finalUrl.pathname) || /(?:xml|json)/i.test(document.contentType)) return "index";
|
|
@@ -17479,7 +17954,7 @@ function resolveMigrationRecord(records, record) {
|
|
|
17479
17954
|
}
|
|
17480
17955
|
return record;
|
|
17481
17956
|
}
|
|
17482
|
-
function navigationConfig(checkpoint, records) {
|
|
17957
|
+
function navigationConfig(checkpoint, records, occurrencePages = /* @__PURE__ */ new Map()) {
|
|
17483
17958
|
const navigation = checkpoint.navigation;
|
|
17484
17959
|
const shortcuts = [];
|
|
17485
17960
|
const collectShortcuts = (nodes) => {
|
|
@@ -17504,7 +17979,7 @@ function navigationConfig(checkpoint, records) {
|
|
|
17504
17979
|
const project2 = (nodes) => nodes.flatMap((node) => {
|
|
17505
17980
|
const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
|
|
17506
17981
|
const target2 = resolveMigrationRecord(records, record);
|
|
17507
|
-
const page = target2?.page;
|
|
17982
|
+
const page = occurrencePages.get(node) ?? target2?.page;
|
|
17508
17983
|
const children = project2(node.children ?? []);
|
|
17509
17984
|
if (children.length) {
|
|
17510
17985
|
const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
|
|
@@ -17804,8 +18279,19 @@ async function runUrlMigration(options) {
|
|
|
17804
18279
|
if (!record.evidence) continue;
|
|
17805
18280
|
const final = canonical(record.evidence.finalUrl, sourceUrl);
|
|
17806
18281
|
if (!final) continue;
|
|
17807
|
-
if (record.outcome === "imported")
|
|
17808
|
-
|
|
18282
|
+
if (record.outcome === "imported") {
|
|
18283
|
+
enqueueArticleLinks(record.evidence.links, final, record.url);
|
|
18284
|
+
for (const reference of record.evidence.markdownRepresentations ?? []) {
|
|
18285
|
+
const alternate = canonical(reference, final);
|
|
18286
|
+
if (!alternate || !sameRouteMarkdownRepresentation(alternate, final)) continue;
|
|
18287
|
+
enqueue(alternate, final, "declared-markdown-representation");
|
|
18288
|
+
const sibling = records.get(alternate);
|
|
18289
|
+
if (sibling && sibling.representationOf !== record.url) {
|
|
18290
|
+
sibling.representationOf = record.url;
|
|
18291
|
+
changed.push(sibling);
|
|
18292
|
+
}
|
|
18293
|
+
}
|
|
18294
|
+
} else if (record.outcome === "excluded") {
|
|
17809
18295
|
if (classifyMigrationResponse({ finalUrl: new URL(final), contentType: record.evidence.contentType, body: "" }) === "index") {
|
|
17810
18296
|
for (const link of record.evidence.links) enqueue(link, final, record.url);
|
|
17811
18297
|
}
|
|
@@ -17888,6 +18374,7 @@ async function runUrlMigration(options) {
|
|
|
17888
18374
|
let claimedId;
|
|
17889
18375
|
let claimedFinal;
|
|
17890
18376
|
let markdownRepresentation;
|
|
18377
|
+
const markdownRepresentations = [];
|
|
17891
18378
|
let exactKey;
|
|
17892
18379
|
try {
|
|
17893
18380
|
delete record.reason;
|
|
@@ -17980,13 +18467,13 @@ async function runUrlMigration(options) {
|
|
|
17980
18467
|
$('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
|
|
17981
18468
|
const alternate = canonical($(node).attr("href"), final);
|
|
17982
18469
|
if (!alternate) return;
|
|
17983
|
-
|
|
17984
|
-
const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
|
|
17985
|
-
if (target2.origin !== primary.origin || target2.search !== primary.search || directory2(target2.pathname) !== directory2(primary.pathname) || !/\.mdx?$/i.test(target2.pathname)) return;
|
|
18470
|
+
if (!sameRouteMarkdownRepresentation(alternate, final)) return;
|
|
17986
18471
|
enqueue(alternate, final, "declared-markdown-representation");
|
|
17987
18472
|
const sibling = records.get(alternate);
|
|
18473
|
+
if (!sibling) return;
|
|
17988
18474
|
sibling.representationOf = record.url;
|
|
17989
18475
|
markdownRepresentation ??= alternate;
|
|
18476
|
+
markdownRepresentations.push(alternate);
|
|
17990
18477
|
changed.push(sibling);
|
|
17991
18478
|
});
|
|
17992
18479
|
}
|
|
@@ -17999,7 +18486,8 @@ async function runUrlMigration(options) {
|
|
|
17999
18486
|
}
|
|
18000
18487
|
enqueueNavigation(observedNavigation, final);
|
|
18001
18488
|
}
|
|
18002
|
-
|
|
18489
|
+
const replacesRepresentation = imported >= maxPages && markdownRepresentations.some((url) => records.get(url)?.page);
|
|
18490
|
+
if (imported >= maxPages && !replacesRepresentation) {
|
|
18003
18491
|
record.outcome = "excluded";
|
|
18004
18492
|
record.reason = "A discovery endpoint resolved to an article after the selected page limit.";
|
|
18005
18493
|
return;
|
|
@@ -18018,14 +18506,17 @@ async function runUrlMigration(options) {
|
|
|
18018
18506
|
const locale = language.locale;
|
|
18019
18507
|
const navigationId = locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id;
|
|
18020
18508
|
const identityId = locale ? `${locale}/${navigationId}` : id;
|
|
18021
|
-
|
|
18509
|
+
const owner = records.get(usedIds.get(identityId) ?? "");
|
|
18510
|
+
const sharesDeclaredRepresentation = owner && (owner.representationOf === record.url || record.representationOf === owner.url);
|
|
18511
|
+
if (owner && owner !== record && !sharesDeclaredRepresentation) {
|
|
18022
18512
|
record.outcome = "failed";
|
|
18023
18513
|
record.reason = `Destination collision for ${identityId}.`;
|
|
18024
18514
|
return;
|
|
18025
18515
|
}
|
|
18026
|
-
usedIds.set(identityId, record.url);
|
|
18516
|
+
if (!owner || owner === record || owner.representationOf === record.url) usedIds.set(identityId, record.url);
|
|
18027
18517
|
claimedId = identityId;
|
|
18028
18518
|
const evidence = evidenceOf(document);
|
|
18519
|
+
if (markdownRepresentations.length) evidence.markdownRepresentations = markdownRepresentations;
|
|
18029
18520
|
const page = convert(document, id);
|
|
18030
18521
|
if (!page) {
|
|
18031
18522
|
record.outcome = "failed";
|
|
@@ -18236,6 +18727,7 @@ ${difference}`;
|
|
|
18236
18727
|
if (!primary || primary === record) continue;
|
|
18237
18728
|
const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
|
|
18238
18729
|
if (!winner) continue;
|
|
18730
|
+
if (winner.page) usedIds.set(winner.page.id, winner.url);
|
|
18239
18731
|
const alternate = winner === primary ? record : primary;
|
|
18240
18732
|
if (alternate.outcome === "alias" && alternate.aliasOf === winner.url) continue;
|
|
18241
18733
|
if (alternate.outcome === "imported") {
|
|
@@ -18257,7 +18749,17 @@ ${difference}`;
|
|
|
18257
18749
|
settleArticleIdentities();
|
|
18258
18750
|
while (cursor < pending.length && !options.signal?.aborted && !budgetExhausted && cursor < (options.maxCaptures ?? Infinity)) {
|
|
18259
18751
|
if (imported >= maxPages) {
|
|
18260
|
-
const index = pending.findIndex((record, position) =>
|
|
18752
|
+
const index = pending.findIndex((record, position) => {
|
|
18753
|
+
if (position < cursor || record.outcome !== "pending") return false;
|
|
18754
|
+
const url = new URL(record.url);
|
|
18755
|
+
if (isDiscoveryIndex(url, source)) return true;
|
|
18756
|
+
if (/\.mdx?$/i.test(url.pathname)) return false;
|
|
18757
|
+
return [".md", ".mdx", "/index.md", "/index.mdx"].some((extension) => {
|
|
18758
|
+
const alternate = new URL(url);
|
|
18759
|
+
alternate.pathname = alternate.pathname.replace(/\/index$/i, "") + extension;
|
|
18760
|
+
return records.get(alternate.href)?.outcome === "imported";
|
|
18761
|
+
});
|
|
18762
|
+
});
|
|
18261
18763
|
if (index < 0) break;
|
|
18262
18764
|
pending.splice(cursor, 0, pending.splice(index, 1)[0]);
|
|
18263
18765
|
}
|
|
@@ -18281,19 +18783,60 @@ ${difference}`;
|
|
|
18281
18783
|
}
|
|
18282
18784
|
const navigationLabels = /* @__PURE__ */ new Map();
|
|
18283
18785
|
const navigationIssues = [];
|
|
18786
|
+
const navigationOccurrences = /* @__PURE__ */ new Map();
|
|
18284
18787
|
const labelPages = (nodes) => {
|
|
18285
18788
|
for (const node of nodes) {
|
|
18286
18789
|
const entry = node.url ? records.get(canonical(node.url, sourceUrl) ?? "") : void 0;
|
|
18287
18790
|
const target2 = resolveMigrationRecord(records, entry);
|
|
18288
|
-
if (target2?.page && node.label && !node.children?.length && !entry
|
|
18289
|
-
const
|
|
18290
|
-
|
|
18291
|
-
|
|
18791
|
+
if (entry && target2?.page && node.label && !node.children?.length && !entry.aliasAnchor) {
|
|
18792
|
+
const occurrences = navigationOccurrences.get(target2) ?? [];
|
|
18793
|
+
occurrences.push({ node, entry });
|
|
18794
|
+
navigationOccurrences.set(target2, occurrences);
|
|
18292
18795
|
}
|
|
18293
18796
|
labelPages(node.children ?? []);
|
|
18294
18797
|
}
|
|
18295
18798
|
};
|
|
18296
18799
|
labelPages(checkpoint.navigation);
|
|
18800
|
+
const occurrencePages = /* @__PURE__ */ new Map();
|
|
18801
|
+
const navigationAliases = /* @__PURE__ */ new Map();
|
|
18802
|
+
const occupiedPageIds = new Set(checkpoint.records.flatMap((record) => record.page ? [projection.pageId(record.page)] : []));
|
|
18803
|
+
const navigationRedirects = [];
|
|
18804
|
+
for (const [target2, occurrences] of navigationOccurrences) {
|
|
18805
|
+
const final = target2.evidence && canonical(target2.evidence.finalUrl, sourceUrl);
|
|
18806
|
+
const primary = occurrences.find(({ entry }) => entry.url === final) ?? occurrences.find(({ entry }) => entry === target2) ?? occurrences[0];
|
|
18807
|
+
navigationLabels.set(target2.url, primary.node.label);
|
|
18808
|
+
for (const { node, entry } of occurrences) {
|
|
18809
|
+
if (node.label === primary.node.label) continue;
|
|
18810
|
+
const existing = navigationAliases.get(entry.url);
|
|
18811
|
+
if (existing?.page.navTitle === node.label && existing.target === target2) {
|
|
18812
|
+
occurrencePages.set(node, existing.page);
|
|
18813
|
+
continue;
|
|
18814
|
+
}
|
|
18815
|
+
const sourceRoute = new URL(entry.url);
|
|
18816
|
+
const id = inScope(sourceRoute, source, scopePath) ? idForUrl(sourceRoute, scopePath) : null;
|
|
18817
|
+
const locale = projection.canonicalLocale(target2.page);
|
|
18818
|
+
let navigationId = id && (locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id);
|
|
18819
|
+
const legacyPrefix = locale && `${locale.toLowerCase()}-`;
|
|
18820
|
+
if (navigationId && legacyPrefix && projection.navigationId(target2.page) !== target2.page.navigationId && navigationId.toLowerCase().startsWith(legacyPrefix)) navigationId = navigationId.slice(legacyPrefix.length);
|
|
18821
|
+
const page = navigationId ? {
|
|
18822
|
+
...target2.page,
|
|
18823
|
+
id: locale ? `${locale}/${navigationId}` : navigationId,
|
|
18824
|
+
navigationId,
|
|
18825
|
+
...locale ? { locale } : {},
|
|
18826
|
+
navTitle: node.label,
|
|
18827
|
+
source: entry.url,
|
|
18828
|
+
noindex: true
|
|
18829
|
+
} : void 0;
|
|
18830
|
+
if (!page || existing || occupiedPageIds.has(projection.pageId(page))) {
|
|
18831
|
+
navigationIssues.push({ code: "unsupported-component", source: target2.url, message: `This page has multiple source navigation labels (${primary.node.label}, ${node.label}); their source routes cannot retain distinct sidebar titles safely.` });
|
|
18832
|
+
continue;
|
|
18833
|
+
}
|
|
18834
|
+
occupiedPageIds.add(projection.pageId(page));
|
|
18835
|
+
navigationAliases.set(entry.url, { page, target: target2 });
|
|
18836
|
+
occurrencePages.set(node, page);
|
|
18837
|
+
navigationRedirects.push({ source: projection.route(page), destination: projection.route(target2.page), permanent: true });
|
|
18838
|
+
}
|
|
18839
|
+
}
|
|
18297
18840
|
const pages = checkpoint.records.flatMap((record) => record.page ? [{ ...record.page, id: projection.pageId(record.page), navigationId: projection.navigationId(record.page), ...projection.canonicalLocale(record.page) ? { locale: projection.canonicalLocale(record.page) } : {}, ...navigationLabels.has(record.url) ? { navTitle: navigationLabels.get(record.url) } : {}, body: rewriteMarkdownDestinations(record.page.body, (value) => {
|
|
18298
18841
|
let url;
|
|
18299
18842
|
try {
|
|
@@ -18314,6 +18857,10 @@ ${difference}`;
|
|
|
18314
18857
|
const hasBrokenSourceAnchor = Boolean(url.hash && destination?.outcome === "imported" && destination.evidence && !destination.evidence.anchors.includes(anchor));
|
|
18315
18858
|
return `${route}${hasBrokenSourceAnchor ? "" : effectiveHash}`;
|
|
18316
18859
|
}) }] : []);
|
|
18860
|
+
const projectedPages = new Map(pages.map((page) => [page.id, page]));
|
|
18861
|
+
for (const { page, target: target2 } of navigationAliases.values()) {
|
|
18862
|
+
pages.push({ ...page, id: projection.pageId(page), body: projectedPages.get(projection.pageId(target2.page)).body });
|
|
18863
|
+
}
|
|
18317
18864
|
const validation = validateUrlMigration(checkpoint, Boolean(options.signal?.aborted));
|
|
18318
18865
|
validation.issues.push(...navigationIssues);
|
|
18319
18866
|
if (navigationIssues.length && validation.status === "passed") validation.status = "partial";
|
|
@@ -18334,12 +18881,12 @@ ${difference}`;
|
|
|
18334
18881
|
platform: options.platform ?? "generic",
|
|
18335
18882
|
pages,
|
|
18336
18883
|
assets: [...assets.values()],
|
|
18337
|
-
docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
|
|
18884
|
+
docsConfig: navigationConfig(checkpoint, records, occurrencePages) ?? buildNavigationFromPages(pages),
|
|
18338
18885
|
warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
|
|
18339
18886
|
urlMigrationValidation: validation,
|
|
18340
18887
|
stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
|
|
18341
18888
|
};
|
|
18342
|
-
if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
|
|
18889
|
+
if (projection.redirects.length || navigationRedirects.length) bundle.docsConfig.redirects = [...projection.redirects, ...navigationRedirects];
|
|
18343
18890
|
if (projection.locales.length) {
|
|
18344
18891
|
const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
|
|
18345
18892
|
bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };
|
|
@@ -18357,9 +18904,11 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
18357
18904
|
const original = body.slice(start, end);
|
|
18358
18905
|
const replacement = rewrite(node.url);
|
|
18359
18906
|
if (replacement === node.url) return null;
|
|
18360
|
-
const
|
|
18361
|
-
|
|
18362
|
-
|
|
18907
|
+
const destinations = postprocess(parse2().document().write(preprocess()(original, void 0, true))).filter(([event, token]) => event === "enter" && ["resourceDestinationString", "definitionDestinationString", "autolinkProtocol", "autolinkEmail"].includes(token.type));
|
|
18908
|
+
const destination = destinations.at(-1)?.[1];
|
|
18909
|
+
if (!destination) return null;
|
|
18910
|
+
const escaped = replacement.replace(/[\\&()<>]/g, "\\$&").replace(/[\t\n\r ]/g, (character) => encodeURIComponent(character));
|
|
18911
|
+
return { start: start + destination.start.offset, end: start + destination.end.offset, replacement: escaped };
|
|
18363
18912
|
}).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
|
|
18364
18913
|
for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
|
|
18365
18914
|
if (!attribute.name || !["src", "poster", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
|
|
@@ -18391,133 +18940,8 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
18391
18940
|
}
|
|
18392
18941
|
|
|
18393
18942
|
// src/remote-api.ts
|
|
18394
|
-
import { createHash as
|
|
18395
|
-
|
|
18396
|
-
import { request } from "https";
|
|
18397
|
-
import { isIP } from "net";
|
|
18398
|
-
var MAX_SPEC_BYTES2 = 25e6;
|
|
18399
|
-
var MAX_REDIRECTS = 3;
|
|
18400
|
-
function isPublicAddress(address, family) {
|
|
18401
|
-
if (family === 4) {
|
|
18402
|
-
const parts = address.split(".").map(Number);
|
|
18403
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
18404
|
-
const [a, b] = parts;
|
|
18405
|
-
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);
|
|
18406
|
-
}
|
|
18407
|
-
return family === 6 && /^[23][0-9a-f]{0,3}:/i.test(address) && !/^2001:db8:/i.test(address);
|
|
18408
|
-
}
|
|
18409
|
-
function validatedSpecUrl(value) {
|
|
18410
|
-
const url = new URL(value);
|
|
18411
|
-
if (url.protocol !== "https:" || url.username || url.password || isIP(url.hostname)) {
|
|
18412
|
-
throw new Error("Remote OpenAPI references must use an HTTPS domain without credentials or an IP literal.");
|
|
18413
|
-
}
|
|
18414
|
-
url.hash = "";
|
|
18415
|
-
return url;
|
|
18416
|
-
}
|
|
18417
|
-
async function getPinnedAddress(url) {
|
|
18418
|
-
const addresses = await lookup(url.hostname, { all: true });
|
|
18419
|
-
if (addresses.length === 0 || addresses.some((candidate) => !isPublicAddress(candidate.address, candidate.family))) {
|
|
18420
|
-
throw new Error("The OpenAPI host did not resolve exclusively to public addresses.");
|
|
18421
|
-
}
|
|
18422
|
-
return addresses[0];
|
|
18423
|
-
}
|
|
18424
|
-
async function requestPinnedSpec(url) {
|
|
18425
|
-
const pinned = await getPinnedAddress(url);
|
|
18426
|
-
return new Promise((resolve5, reject) => {
|
|
18427
|
-
const req = request(url, {
|
|
18428
|
-
headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
|
|
18429
|
-
signal: AbortSignal.timeout(3e4),
|
|
18430
|
-
// Node's dual-stack connector asks lookup for `all: true`; that
|
|
18431
|
-
// callback must receive address records, not a single address string.
|
|
18432
|
-
lookup: (_hostname, options, callback) => {
|
|
18433
|
-
if (typeof options === "object" && options.all) callback(null, [pinned]);
|
|
18434
|
-
else callback(null, pinned.address, pinned.family);
|
|
18435
|
-
}
|
|
18436
|
-
}, (response) => {
|
|
18437
|
-
const status = response.statusCode ?? 0;
|
|
18438
|
-
if (status >= 300 && status < 400) {
|
|
18439
|
-
response.resume();
|
|
18440
|
-
resolve5({ status, location: response.headers.location, body: Buffer.alloc(0) });
|
|
18441
|
-
return;
|
|
18442
|
-
}
|
|
18443
|
-
if (status < 200 || status >= 300) {
|
|
18444
|
-
response.resume();
|
|
18445
|
-
reject(new Error(`OpenAPI server returned ${status}.`));
|
|
18446
|
-
return;
|
|
18447
|
-
}
|
|
18448
|
-
const statedBytes = Number(response.headers["content-length"] ?? 0);
|
|
18449
|
-
if (statedBytes > MAX_SPEC_BYTES2) {
|
|
18450
|
-
response.destroy();
|
|
18451
|
-
reject(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
18452
|
-
return;
|
|
18453
|
-
}
|
|
18454
|
-
const chunks = [];
|
|
18455
|
-
let bytes = 0;
|
|
18456
|
-
response.on("data", (chunk) => {
|
|
18457
|
-
bytes += chunk.length;
|
|
18458
|
-
if (bytes > MAX_SPEC_BYTES2) {
|
|
18459
|
-
response.destroy(new Error("Remote OpenAPI spec exceeds the 25 MB import limit."));
|
|
18460
|
-
return;
|
|
18461
|
-
}
|
|
18462
|
-
chunks.push(chunk);
|
|
18463
|
-
});
|
|
18464
|
-
response.on("end", () => resolve5({ status, body: Buffer.concat(chunks) }));
|
|
18465
|
-
response.on("error", reject);
|
|
18466
|
-
});
|
|
18467
|
-
req.on("error", reject);
|
|
18468
|
-
req.end();
|
|
18469
|
-
});
|
|
18470
|
-
}
|
|
18471
|
-
async function downloadSpec(value, fetcher) {
|
|
18472
|
-
let url = validatedSpecUrl(value);
|
|
18473
|
-
if (fetcher) {
|
|
18474
|
-
const response = await fetcher(url, { accept: "application/json, application/yaml, text/yaml" });
|
|
18475
|
-
validatedSpecUrl(response.finalUrl.toString());
|
|
18476
|
-
const body = Buffer.from(response.body);
|
|
18477
|
-
if (body.length > MAX_SPEC_BYTES2) throw new Error("Remote OpenAPI spec exceeds the 25 MB import limit.");
|
|
18478
|
-
return body;
|
|
18479
|
-
}
|
|
18480
|
-
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
18481
|
-
const response = await requestPinnedSpec(url);
|
|
18482
|
-
if (response.status >= 300 && response.status < 400) {
|
|
18483
|
-
if (!response.location || hop === MAX_REDIRECTS) throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
18484
|
-
url = validatedSpecUrl(new URL(response.location, url).toString());
|
|
18485
|
-
continue;
|
|
18486
|
-
}
|
|
18487
|
-
return response.body;
|
|
18488
|
-
}
|
|
18489
|
-
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
18490
|
-
}
|
|
18491
|
-
function parseOpenApi(parsed) {
|
|
18492
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
18493
|
-
const document = parsed;
|
|
18494
|
-
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
18495
|
-
throw new Error("Remote specification is not OpenAPI 3.x.");
|
|
18496
|
-
}
|
|
18497
|
-
if (!document.paths || typeof document.paths !== "object" || Array.isArray(document.paths)) {
|
|
18498
|
-
throw new Error("Remote OpenAPI spec has no paths object.");
|
|
18499
|
-
}
|
|
18500
|
-
const paths = Object.entries(document.paths);
|
|
18501
|
-
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
18502
|
-
const operations = [];
|
|
18503
|
-
for (const [path, value] of paths) {
|
|
18504
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || isWithheld(value)) continue;
|
|
18505
|
-
for (const [method, raw] of Object.entries(value)) {
|
|
18506
|
-
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
18507
|
-
const operation = raw;
|
|
18508
|
-
if (isWithheld(operation)) continue;
|
|
18509
|
-
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
18510
|
-
operations.push({
|
|
18511
|
-
method: method.toLowerCase(),
|
|
18512
|
-
path,
|
|
18513
|
-
tag: typeof tags[0] === "string" ? tags[0] : "API Reference",
|
|
18514
|
-
summary: typeof operation.summary === "string" ? operation.summary : String(operation.operationId ?? "")
|
|
18515
|
-
});
|
|
18516
|
-
}
|
|
18517
|
-
}
|
|
18518
|
-
return operations;
|
|
18519
|
-
}
|
|
18520
|
-
function slug(value) {
|
|
18943
|
+
import { createHash as createHash8 } from "crypto";
|
|
18944
|
+
function slug2(value) {
|
|
18521
18945
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
18522
18946
|
}
|
|
18523
18947
|
function operationTarget(specId, operation) {
|
|
@@ -18566,7 +18990,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
18566
18990
|
}
|
|
18567
18991
|
const { format: extension, document } = parseSpecDocument(body.toString("utf8"));
|
|
18568
18992
|
const operations = parseOpenApi(document);
|
|
18569
|
-
const filename = `openapi-${
|
|
18993
|
+
const filename = `openapi-${createHash8("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
18570
18994
|
if (!assets.some((asset) => asset.path === `openapi/${filename}`)) assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
18571
18995
|
for (const page of pages) {
|
|
18572
18996
|
const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
|
|
@@ -18651,7 +19075,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
18651
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;
|
|
18652
19076
|
for (const operation of operations) {
|
|
18653
19077
|
if (listed && !listed.has(`${operation.method} ${operation.path}`)) continue;
|
|
18654
|
-
const key = `${
|
|
19078
|
+
const key = `${slug2(operation.tag)}/${slug2(operation.summary)}`;
|
|
18655
19079
|
guesses.set(key, guesses.has(key) || !specId ? null : operationTarget(specId, operation));
|
|
18656
19080
|
}
|
|
18657
19081
|
}
|
|
@@ -18694,12 +19118,16 @@ export {
|
|
|
18694
19118
|
detectRepositoryPlatform,
|
|
18695
19119
|
gitmodulePaths,
|
|
18696
19120
|
hydrateRemoteApiSpecs,
|
|
19121
|
+
hydrateUrlOpenApi,
|
|
18697
19122
|
importSourceRef,
|
|
18698
19123
|
isPassiveSvg,
|
|
19124
|
+
literalRoute,
|
|
18699
19125
|
mergeMigrationConfig,
|
|
18700
19126
|
migrateRepository,
|
|
18701
19127
|
migrateUrl,
|
|
19128
|
+
migrationApiRoutes,
|
|
18702
19129
|
normalizeMdx,
|
|
19130
|
+
pageEntryRoutes,
|
|
18703
19131
|
parseGitHubRepositoryUrl,
|
|
18704
19132
|
parseMarkdownPage,
|
|
18705
19133
|
parseSourceRefFlags,
|