@thallylabs/migrate 0.2.31 → 0.2.33
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 +54 -8
- package/dist/index.d.ts +13 -5
- package/dist/index.js +470 -158
- package/dist/index.js.map +1 -1
- package/dist/{url-migration-Coy62q1L.d.ts → url-migration-_wVauqSg.d.ts} +19 -0
- package/dist/url-migration.d.ts +1 -1
- package/dist/url-migration.js +71 -27
- package/dist/url-migration.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2393,9 +2393,9 @@ function escapeOrphanCapitalizedTags(body) {
|
|
|
2393
2393
|
continue;
|
|
2394
2394
|
}
|
|
2395
2395
|
const start = match.index;
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
opens.set(name,
|
|
2396
|
+
const list2 = opens.get(name) ?? [];
|
|
2397
|
+
list2.push({ start, end: start + full.length });
|
|
2398
|
+
opens.set(name, list2);
|
|
2399
2399
|
}
|
|
2400
2400
|
const edits = [];
|
|
2401
2401
|
for (const [name, ranges] of opens) {
|
|
@@ -4238,24 +4238,25 @@ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as rea
|
|
|
4238
4238
|
import { dirname as dirname2, extname as extname3, relative as relative3 } from "path";
|
|
4239
4239
|
var MAX_MINTLIFY_CONFIG_BYTES = 2e7;
|
|
4240
4240
|
function addMintlifyDirectoryRedirects(config, pages) {
|
|
4241
|
-
const pageIds = new Set(pages.map((page) => page.id));
|
|
4241
|
+
const pageIds = new Set(pages.map((page) => page.id.toLowerCase()));
|
|
4242
4242
|
const redirects = [...config.redirects ?? []];
|
|
4243
|
-
const redirectSources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
4243
|
+
const redirectSources = new Set(redirects.map((redirect) => (redirect.source.replace(/\/$/, "") || "/").toLowerCase()));
|
|
4244
4244
|
function hasAuthoredRedirect(source) {
|
|
4245
|
-
|
|
4245
|
+
const route = source.toLowerCase();
|
|
4246
|
+
if (redirectSources.has(route)) return true;
|
|
4246
4247
|
return (config.redirects ?? []).some((redirect) => {
|
|
4247
|
-
const match = redirect.source.match(/^(.*)\/:[
|
|
4248
|
-
return match ?
|
|
4248
|
+
const match = redirect.source.toLowerCase().match(/^(.*)\/:[a-z_][a-z0-9_]*\*\/?$/);
|
|
4249
|
+
return match ? route === match[1] || route.startsWith(`${match[1]}/`) : false;
|
|
4249
4250
|
});
|
|
4250
4251
|
}
|
|
4251
4252
|
for (const page of pages) {
|
|
4252
4253
|
if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
|
|
4253
4254
|
const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
|
|
4254
|
-
if (!parent || pageIds.has(parent)) continue;
|
|
4255
|
+
if (!parent || pageIds.has(parent.toLowerCase())) continue;
|
|
4255
4256
|
const source = `/${parent}`;
|
|
4256
4257
|
if (hasAuthoredRedirect(source)) continue;
|
|
4257
4258
|
redirects.push({ source, destination: `/${page.id}`, permanent: false });
|
|
4258
|
-
redirectSources.add(source);
|
|
4259
|
+
redirectSources.add(source.toLowerCase());
|
|
4259
4260
|
}
|
|
4260
4261
|
const navigationOrder = [];
|
|
4261
4262
|
function collectPages(nodes) {
|
|
@@ -4285,11 +4286,11 @@ function addMintlifyDirectoryRedirects(config, pages) {
|
|
|
4285
4286
|
}
|
|
4286
4287
|
}
|
|
4287
4288
|
for (const [directory, landing] of firstDescendantByDirectory) {
|
|
4288
|
-
if (pageIds.has(directory)) continue;
|
|
4289
|
+
if (pageIds.has(directory.toLowerCase())) continue;
|
|
4289
4290
|
const source = `/${directory}`;
|
|
4290
4291
|
if (hasAuthoredRedirect(source)) continue;
|
|
4291
4292
|
redirects.push({ source, destination: `/${landing.id}`, permanent: false });
|
|
4292
|
-
redirectSources.add(source);
|
|
4293
|
+
redirectSources.add(source.toLowerCase());
|
|
4293
4294
|
}
|
|
4294
4295
|
return redirects.length > 0 ? { ...config, redirects } : config;
|
|
4295
4296
|
}
|
|
@@ -4308,11 +4309,11 @@ function addMintlifyHomepageRedirects(config, pages) {
|
|
|
4308
4309
|
const homepage = config.tabs.filter((tab) => !tab.hidden).map((tab) => firstPage([...tab.pages ?? [], ...tab.groups ?? []])).find(Boolean);
|
|
4309
4310
|
if (!homepage) return config;
|
|
4310
4311
|
const redirects = [...config.redirects ?? []];
|
|
4311
|
-
const sources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
4312
|
+
const sources = new Set(redirects.map((redirect) => (redirect.source.replace(/\/$/, "") || "/").toLowerCase()));
|
|
4312
4313
|
const roots = ["", ...(config.i18n?.locales ?? []).filter((locale) => locale.code !== config.i18n?.defaultLocale).filter((locale) => homepage !== "introduction" || pages.some((page) => page.locale === locale.code && page.navigationId === homepage)).map((locale) => locale.code)];
|
|
4313
4314
|
for (const locale of roots) {
|
|
4314
4315
|
const source = locale ? `/${locale}` : "/";
|
|
4315
|
-
if (sources.has(source)) continue;
|
|
4316
|
+
if (sources.has(source.toLowerCase())) continue;
|
|
4316
4317
|
redirects.push({ source, destination: `${locale ? `/${locale}` : ""}/${homepage}`, permanent: true });
|
|
4317
4318
|
}
|
|
4318
4319
|
return { ...config, redirects };
|
|
@@ -4439,6 +4440,22 @@ function specSource(value) {
|
|
|
4439
4440
|
const source = typeof value === "string" ? value : objectValue2(value)?.source;
|
|
4440
4441
|
return typeof source === "string" && source.trim() ? source.trim() : void 0;
|
|
4441
4442
|
}
|
|
4443
|
+
function listedOperations(container) {
|
|
4444
|
+
const operations = [];
|
|
4445
|
+
const visit = (entry, root) => {
|
|
4446
|
+
if (typeof entry === "string") {
|
|
4447
|
+
if (OPENAPI_OPERATION_REF.test(entry)) operations.push(entry.trim());
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
const object2 = objectValue2(entry);
|
|
4451
|
+
if (!object2 || !root && (specSource(object2.openapi) || specSource(object2.asyncapi))) return;
|
|
4452
|
+
for (const key of ["groups", "pages"]) {
|
|
4453
|
+
if (Array.isArray(object2[key])) object2[key].forEach((child) => visit(child, false));
|
|
4454
|
+
}
|
|
4455
|
+
};
|
|
4456
|
+
visit(container, true);
|
|
4457
|
+
return operations;
|
|
4458
|
+
}
|
|
4442
4459
|
var hasNavigationChildren = (node) => ["groups", "pages"].some((key) => Array.isArray(node[key]) && node[key].length > 0);
|
|
4443
4460
|
function mintlifyNavigationApiReferences(config) {
|
|
4444
4461
|
const navigation = objectValue2(config.navigation) ?? config;
|
|
@@ -4450,12 +4467,14 @@ function mintlifyNavigationApiReferences(config) {
|
|
|
4450
4467
|
const value = specSource(node[kind]);
|
|
4451
4468
|
const directory = objectValue2(node[kind])?.directory;
|
|
4452
4469
|
if (value) {
|
|
4470
|
+
const operations = typeof directory === "string" && directory.trim() ? listedOperations(node) : [];
|
|
4453
4471
|
references.push({
|
|
4454
4472
|
value,
|
|
4455
4473
|
kind,
|
|
4456
4474
|
tabLabel,
|
|
4457
4475
|
...sibling,
|
|
4458
|
-
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {}
|
|
4476
|
+
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {},
|
|
4477
|
+
...operations.length > 0 ? { operations } : {}
|
|
4459
4478
|
});
|
|
4460
4479
|
}
|
|
4461
4480
|
}
|
|
@@ -7206,16 +7225,32 @@ function withheldOperationKeys(spec, withheldRefs, keptRefs) {
|
|
|
7206
7225
|
const kept = keysFor(spec, keptRefs);
|
|
7207
7226
|
return { withheld: keysFor(spec, withheldRefs).filter((key) => !kept.includes(key)), kept };
|
|
7208
7227
|
}
|
|
7228
|
+
function withoutByteOrderMark(content) {
|
|
7229
|
+
return content.length >= 3 && content[0] === 239 && content[1] === 187 && content[2] === 191 ? content.subarray(3) : content;
|
|
7230
|
+
}
|
|
7231
|
+
function parseSpecDocument(text3) {
|
|
7232
|
+
const source = text3.replace(/^\uFEFF/, "");
|
|
7233
|
+
try {
|
|
7234
|
+
return { format: "json", document: JSON.parse(source) };
|
|
7235
|
+
} catch (jsonError) {
|
|
7236
|
+
try {
|
|
7237
|
+
return { format: "yaml", document: parseYaml4(source) };
|
|
7238
|
+
} catch (yamlError) {
|
|
7239
|
+
throw source.trimStart().startsWith("{") ? jsonError : yamlError;
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7209
7243
|
function markExcluded(content, operationKeys, keptKeys = []) {
|
|
7210
7244
|
const untouched = { content, excluded: [] };
|
|
7211
7245
|
if (operationKeys.length === 0) return untouched;
|
|
7212
7246
|
const text3 = content.toString("utf8");
|
|
7213
|
-
let
|
|
7247
|
+
let parsed;
|
|
7214
7248
|
try {
|
|
7215
|
-
|
|
7249
|
+
parsed = parseSpecDocument(text3);
|
|
7216
7250
|
} catch {
|
|
7217
7251
|
return untouched;
|
|
7218
7252
|
}
|
|
7253
|
+
const doc = parsed.document;
|
|
7219
7254
|
if (!doc || typeof doc !== "object") return untouched;
|
|
7220
7255
|
const excluded = [];
|
|
7221
7256
|
for (const key of operationKeys) {
|
|
@@ -7238,7 +7273,7 @@ function markExcluded(content, operationKeys, keptKeys = []) {
|
|
|
7238
7273
|
}
|
|
7239
7274
|
}
|
|
7240
7275
|
if (excluded.length === 0) return untouched;
|
|
7241
|
-
const out =
|
|
7276
|
+
const out = parsed.format === "json" ? `${JSON.stringify(doc, null, 2)}
|
|
7242
7277
|
` : stringifyYaml(doc);
|
|
7243
7278
|
return { content: Buffer.from(out), excluded };
|
|
7244
7279
|
}
|
|
@@ -7469,6 +7504,150 @@ function migrationImageAttribute(value) {
|
|
|
7469
7504
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/\{/g, "{").replace(/\}/g, "}").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
7470
7505
|
}
|
|
7471
7506
|
|
|
7507
|
+
// src/redirects.ts
|
|
7508
|
+
var LISTED = 10;
|
|
7509
|
+
function exactRoute(route) {
|
|
7510
|
+
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route.replace(/\/+$/, "") || "/" : null;
|
|
7511
|
+
}
|
|
7512
|
+
function literalRoute(route) {
|
|
7513
|
+
return exactRoute(route)?.toLowerCase() ?? null;
|
|
7514
|
+
}
|
|
7515
|
+
function resolveDotSegments(route) {
|
|
7516
|
+
const segments = [];
|
|
7517
|
+
for (const segment of route.split("/").slice(1)) {
|
|
7518
|
+
const dots = segment.replace(/%2e/gi, ".");
|
|
7519
|
+
if (dots === "..") segments.pop();
|
|
7520
|
+
else if (dots !== ".") segments.push(segment);
|
|
7521
|
+
}
|
|
7522
|
+
return `/${segments.join("/")}`;
|
|
7523
|
+
}
|
|
7524
|
+
function destinationRoute(destination) {
|
|
7525
|
+
const route = destination.startsWith("//") ? null : exactRoute(destination.split(/[?#]/, 1)[0]);
|
|
7526
|
+
return route === null ? null : resolveDotSegments(route);
|
|
7527
|
+
}
|
|
7528
|
+
function pageRoutes(pages) {
|
|
7529
|
+
return pages.flatMap((page) => [`/${page.id}`, `/${page.id.replace(/\/index$/, "")}`]);
|
|
7530
|
+
}
|
|
7531
|
+
function servedRoutes(pages) {
|
|
7532
|
+
return new Set(pageRoutes(pages).map((route) => route.toLowerCase()));
|
|
7533
|
+
}
|
|
7534
|
+
function list(sources) {
|
|
7535
|
+
return `${sources.slice(0, LISTED).join(", ")}${sources.length > LISTED ? ", ..." : ""}`;
|
|
7536
|
+
}
|
|
7537
|
+
var ROUTE_PATTERN_OPERATORS = /[+?()[\]{}]/;
|
|
7538
|
+
function dropUnroutableRedirects(config, warnings) {
|
|
7539
|
+
if (!config.redirects?.length) return config;
|
|
7540
|
+
const unmatchable = [];
|
|
7541
|
+
const redirects = config.redirects.filter((redirect) => {
|
|
7542
|
+
const source = exactRoute(redirect.source);
|
|
7543
|
+
if (source && source === exactRoute(redirect.destination)) {
|
|
7544
|
+
warnings.push({ code: "unsupported-config", message: `Self-redirect from ${redirect.source} to ${redirect.destination} was omitted.` });
|
|
7545
|
+
return false;
|
|
7546
|
+
}
|
|
7547
|
+
if (!ROUTE_PATTERN_OPERATORS.test(redirect.source)) return true;
|
|
7548
|
+
unmatchable.push(redirect.source);
|
|
7549
|
+
return false;
|
|
7550
|
+
});
|
|
7551
|
+
if (unmatchable.length > 0) {
|
|
7552
|
+
warnings.push({
|
|
7553
|
+
code: "unsupported-config",
|
|
7554
|
+
message: `Skipped ${unmatchable.length} redirect${unmatchable.length === 1 ? "" : "s"} from ${list(unmatchable)}: ${unmatchable.length === 1 ? "that address has" : "those addresses have"} a character (+ ? ( ) [ ] { }) that Next.js reads as route syntax and cannot match literally, so visits to ${unmatchable.length === 1 ? "it" : "them"} will not redirect.`
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
7557
|
+
if (redirects.length === config.redirects.length) return config;
|
|
7558
|
+
const result = { ...config, redirects };
|
|
7559
|
+
if (redirects.length === 0) delete result.redirects;
|
|
7560
|
+
return result;
|
|
7561
|
+
}
|
|
7562
|
+
function redirectChains() {
|
|
7563
|
+
const kept = /* @__PURE__ */ new Map();
|
|
7564
|
+
const terminal = /* @__PURE__ */ new Set();
|
|
7565
|
+
const has = (source) => kept.has(source) || terminal.has(source);
|
|
7566
|
+
const add = (source, destination) => {
|
|
7567
|
+
if (destination === null) terminal.add(source);
|
|
7568
|
+
else kept.set(source, destination);
|
|
7569
|
+
};
|
|
7570
|
+
const closesLoop = (source, destination) => {
|
|
7571
|
+
let end = destination;
|
|
7572
|
+
for (let steps = 0; kept.has(end); steps++) {
|
|
7573
|
+
if (steps >= kept.size) return true;
|
|
7574
|
+
end = kept.get(end);
|
|
7575
|
+
}
|
|
7576
|
+
for (let step = destination; step !== end; ) {
|
|
7577
|
+
const next = kept.get(step);
|
|
7578
|
+
kept.set(step, end);
|
|
7579
|
+
step = next;
|
|
7580
|
+
}
|
|
7581
|
+
return end === source;
|
|
7582
|
+
};
|
|
7583
|
+
return { has, add, closesLoop };
|
|
7584
|
+
}
|
|
7585
|
+
function dropConflictingRedirects(config, pages, warnings) {
|
|
7586
|
+
if (!config.redirects?.length) return config;
|
|
7587
|
+
const served = servedRoutes(pages);
|
|
7588
|
+
const servedAsWritten = new Set(pageRoutes(pages));
|
|
7589
|
+
const hidden = [];
|
|
7590
|
+
const hiddenByCase = [];
|
|
7591
|
+
const looping = [];
|
|
7592
|
+
const loopingByCase = [];
|
|
7593
|
+
const folded = redirectChains();
|
|
7594
|
+
const asWritten = redirectChains();
|
|
7595
|
+
const redirects = config.redirects.filter((redirect) => {
|
|
7596
|
+
const source = literalRoute(redirect.source);
|
|
7597
|
+
if (!source) return true;
|
|
7598
|
+
const writtenSource = exactRoute(redirect.source);
|
|
7599
|
+
if (served.has(source)) {
|
|
7600
|
+
const hides = servedAsWritten.has(writtenSource) ? hidden : hiddenByCase;
|
|
7601
|
+
hides.push(redirect.source);
|
|
7602
|
+
return false;
|
|
7603
|
+
}
|
|
7604
|
+
const writtenDestination = destinationRoute(redirect.destination);
|
|
7605
|
+
const destination = writtenDestination?.toLowerCase() ?? null;
|
|
7606
|
+
const firesFolded = !folded.has(source);
|
|
7607
|
+
const firesAsWritten = !asWritten.has(writtenSource);
|
|
7608
|
+
const loopsFolded = firesFolded && destination !== null && folded.closesLoop(source, destination);
|
|
7609
|
+
const loopsAsWritten = firesAsWritten && writtenDestination !== null && asWritten.closesLoop(writtenSource, writtenDestination);
|
|
7610
|
+
if (loopsFolded || loopsAsWritten) {
|
|
7611
|
+
const loops = loopsAsWritten ? looping : loopingByCase;
|
|
7612
|
+
loops.push(`${redirect.source} -> ${redirect.destination}`);
|
|
7613
|
+
return false;
|
|
7614
|
+
}
|
|
7615
|
+
if (firesFolded) folded.add(source, destination);
|
|
7616
|
+
if (firesAsWritten) asWritten.add(writtenSource, writtenDestination);
|
|
7617
|
+
return true;
|
|
7618
|
+
});
|
|
7619
|
+
if (hidden.length > 0) {
|
|
7620
|
+
warnings.push({
|
|
7621
|
+
code: "unsupported-config",
|
|
7622
|
+
message: `Skipped ${hidden.length} redirect${hidden.length === 1 ? "" : "s"} from ${list(hidden)}: ${hidden.length === 1 ? "that URL is" : "those URLs are"} also ${hidden.length === 1 ? "a page" : "pages"} in this site, and a redirect would hide ${hidden.length === 1 ? "it" : "them"}. To redirect instead, delete the page and add the redirect to docs.json.`
|
|
7623
|
+
});
|
|
7624
|
+
}
|
|
7625
|
+
if (hiddenByCase.length > 0) {
|
|
7626
|
+
const one = hiddenByCase.length === 1;
|
|
7627
|
+
warnings.push({
|
|
7628
|
+
code: "unsupported-config",
|
|
7629
|
+
message: `Skipped ${hiddenByCase.length} redirect${one ? "" : "s"} from ${list(hiddenByCase)}: ${one ? "that URL differs" : "those URLs differ"} only in letter case from ${one ? "a page" : "pages"} in this site, so on a server that ignores letter case ${one ? "it" : "they"} would hide ${one ? "that page" : "those pages"}. If your host matches URLs case-sensitively, add ${one ? "it" : "them"} back to docs.json.`
|
|
7630
|
+
});
|
|
7631
|
+
}
|
|
7632
|
+
if (looping.length > 0) {
|
|
7633
|
+
warnings.push({
|
|
7634
|
+
code: "unsupported-config",
|
|
7635
|
+
message: `Skipped ${looping.length} redirect${looping.length === 1 ? "" : "s"} that would send visitors in a circle: ${list(looping)}. Check these redirects in the source docs.`
|
|
7636
|
+
});
|
|
7637
|
+
}
|
|
7638
|
+
if (loopingByCase.length > 0) {
|
|
7639
|
+
const one = loopingByCase.length === 1;
|
|
7640
|
+
warnings.push({
|
|
7641
|
+
code: "unsupported-config",
|
|
7642
|
+
message: `Skipped ${loopingByCase.length} redirect${one ? "" : "s"} that would send visitors in a circle on a server that ignores letter case, since the addresses involved differ only in letter case: ${list(loopingByCase)}. If your host matches URLs case-sensitively, add ${one ? "it" : "them"} back to docs.json.`
|
|
7643
|
+
});
|
|
7644
|
+
}
|
|
7645
|
+
if (redirects.length === config.redirects.length) return config;
|
|
7646
|
+
const result = { ...config, redirects };
|
|
7647
|
+
if (redirects.length === 0) delete result.redirects;
|
|
7648
|
+
return result;
|
|
7649
|
+
}
|
|
7650
|
+
|
|
7472
7651
|
// src/url.ts
|
|
7473
7652
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
7474
7653
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
@@ -8443,13 +8622,13 @@ function migrationHtmlArticle(body) {
|
|
|
8443
8622
|
const sourceId = select.attr("id");
|
|
8444
8623
|
if (sourceId) projection.attr("id", sourceId);
|
|
8445
8624
|
projection.append($("<p></p>").text(select.attr("aria-label")));
|
|
8446
|
-
const
|
|
8625
|
+
const list2 = $("<ul></ul>");
|
|
8447
8626
|
for (const url of urls) {
|
|
8448
8627
|
const item = $("<li></li>").append($("<a></a>").attr("href", url.href).text(url.label));
|
|
8449
8628
|
if (url.isSelected) item.append(" (selected)");
|
|
8450
|
-
|
|
8629
|
+
list2.append(item);
|
|
8451
8630
|
}
|
|
8452
|
-
projection.append(
|
|
8631
|
+
projection.append(list2);
|
|
8453
8632
|
select.replaceWith(projection);
|
|
8454
8633
|
});
|
|
8455
8634
|
return { $, root };
|
|
@@ -8691,8 +8870,8 @@ function readDocusaurusSidebar(document) {
|
|
|
8691
8870
|
const $ = load2(document.body);
|
|
8692
8871
|
const sidebar = $("ul.theme-doc-sidebar-menu").first();
|
|
8693
8872
|
if (sidebar.length === 0) return null;
|
|
8694
|
-
function readList(
|
|
8695
|
-
return $(
|
|
8873
|
+
function readList(list2) {
|
|
8874
|
+
return $(list2).children("li").toArray().flatMap((item) => {
|
|
8696
8875
|
const directLink = $(item).children("a.menu__link").first();
|
|
8697
8876
|
const collapsibleLink = $(item).children(".menu__list-item-collapsible").children("a.menu__link").first();
|
|
8698
8877
|
const link = directLink.length > 0 ? directLink : collapsibleLink;
|
|
@@ -9255,6 +9434,7 @@ async function migrateUrl(options) {
|
|
|
9255
9434
|
};
|
|
9256
9435
|
}
|
|
9257
9436
|
if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
|
|
9437
|
+
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), pages, warnings);
|
|
9258
9438
|
return {
|
|
9259
9439
|
sourceUrl: source.toString(),
|
|
9260
9440
|
sourceKind: "url",
|
|
@@ -10402,7 +10582,7 @@ function readSpecFile(absolutePath, relativePath, warnings) {
|
|
|
10402
10582
|
});
|
|
10403
10583
|
return void 0;
|
|
10404
10584
|
}
|
|
10405
|
-
return readFileSync6(absolutePath);
|
|
10585
|
+
return withoutByteOrderMark(readFileSync6(absolutePath));
|
|
10406
10586
|
}
|
|
10407
10587
|
function specAssetPath(filename) {
|
|
10408
10588
|
return `${SPEC_DIRECTORY}/${filename}`;
|
|
@@ -10438,10 +10618,26 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
10438
10618
|
references.push({ value: key, kind: "openapi", tabLabel: `OpenAPI: ${key}`, hidden: true, pageOnly: true });
|
|
10439
10619
|
}
|
|
10440
10620
|
const seen = /* @__PURE__ */ new Set();
|
|
10621
|
+
const remoteByKey = /* @__PURE__ */ new Map();
|
|
10622
|
+
const mountDirectory = (remote, reference) => {
|
|
10623
|
+
const directory = reference.directory?.replace(/^\/+|\/+$/g, "");
|
|
10624
|
+
if (!remote || !directory) return;
|
|
10625
|
+
const mount = remote.directories?.find((existing) => existing.directory === directory);
|
|
10626
|
+
if (!mount) {
|
|
10627
|
+
remote.directories = [...remote.directories ?? [], { directory, ...reference.operations ? { operations: reference.operations } : {} }];
|
|
10628
|
+
} else if (mount.operations && reference.operations) {
|
|
10629
|
+
mount.operations = [.../* @__PURE__ */ new Set([...mount.operations, ...reference.operations])];
|
|
10630
|
+
} else {
|
|
10631
|
+
delete mount.operations;
|
|
10632
|
+
}
|
|
10633
|
+
};
|
|
10441
10634
|
const specs = [];
|
|
10442
10635
|
for (const reference of references) {
|
|
10443
10636
|
const dedupeKey = `${reference.kind}:${reference.value}:${reference.tabLabel ?? ""}`;
|
|
10444
|
-
if (seen.has(dedupeKey))
|
|
10637
|
+
if (seen.has(dedupeKey)) {
|
|
10638
|
+
mountDirectory(remoteByKey.get(dedupeKey), reference);
|
|
10639
|
+
continue;
|
|
10640
|
+
}
|
|
10445
10641
|
seen.add(dedupeKey);
|
|
10446
10642
|
const tabSuffix = reference.tabLabel ? ` (tab "${reference.tabLabel}")` : "";
|
|
10447
10643
|
if (reference.kind === "asyncapi") {
|
|
@@ -10452,7 +10648,10 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
10452
10648
|
continue;
|
|
10453
10649
|
}
|
|
10454
10650
|
if (/^https:\/\//i.test(reference.value)) {
|
|
10455
|
-
|
|
10651
|
+
const remote = { url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {}, ...reference.parentTab ? { parentTab: reference.parentTab } : {}, ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {} };
|
|
10652
|
+
mountDirectory(remote, reference);
|
|
10653
|
+
remoteSpecs.push(remote);
|
|
10654
|
+
remoteByKey.set(dedupeKey, remote);
|
|
10456
10655
|
warnings.push({
|
|
10457
10656
|
code: "unsupported-config",
|
|
10458
10657
|
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
@@ -10552,10 +10751,15 @@ function thallyOperationSlugSegments(path, method) {
|
|
|
10552
10751
|
function slugifyApiSpecId(value) {
|
|
10553
10752
|
return value.toLowerCase().replace(/[^a-z0-9/]+/g, "-").replace(/(^-|-$)+/g, "").replace(/\//g, "-") || value.toLowerCase();
|
|
10554
10753
|
}
|
|
10754
|
+
function apiTabSpecIds(tabs) {
|
|
10755
|
+
return tabs.filter((tab) => !tab.hidden && tab.api).map((tab, index) => ({ tab, specId: index === 0 ? "default" : slugifyApiSpecId(tab.tab) }));
|
|
10756
|
+
}
|
|
10757
|
+
var flagged = (value) => value === true || value === "true";
|
|
10758
|
+
var isWithheld = (node) => flagged(node["x-hidden"]) || flagged(node["x-excluded"]);
|
|
10555
10759
|
function parseOpenApiOperationsForLinking(content) {
|
|
10556
10760
|
let document;
|
|
10557
10761
|
try {
|
|
10558
|
-
document =
|
|
10762
|
+
document = parseSpecDocument(content.toString("utf8")).document;
|
|
10559
10763
|
} catch {
|
|
10560
10764
|
return [];
|
|
10561
10765
|
}
|
|
@@ -10564,10 +10768,13 @@ function parseOpenApiOperationsForLinking(content) {
|
|
|
10564
10768
|
const operations = [];
|
|
10565
10769
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
10566
10770
|
if (!pathItem || typeof pathItem !== "object") continue;
|
|
10771
|
+
const item = pathItem;
|
|
10772
|
+
if (isWithheld(item)) continue;
|
|
10567
10773
|
for (const method of OPENAPI_HTTP_METHODS) {
|
|
10568
|
-
const operation =
|
|
10774
|
+
const operation = item[method];
|
|
10569
10775
|
if (!operation || typeof operation !== "object") continue;
|
|
10570
10776
|
const op = operation;
|
|
10777
|
+
if (isWithheld(op)) continue;
|
|
10571
10778
|
const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
|
|
10572
10779
|
operations.push({
|
|
10573
10780
|
method,
|
|
@@ -10580,19 +10787,39 @@ function parseOpenApiOperationsForLinking(content) {
|
|
|
10580
10787
|
}
|
|
10581
10788
|
return operations;
|
|
10582
10789
|
}
|
|
10583
|
-
function
|
|
10790
|
+
function servedOperations(source, parsed) {
|
|
10791
|
+
let operations = parsed.get(source.filename);
|
|
10792
|
+
if (!operations) {
|
|
10793
|
+
operations = parseOpenApiOperationsForLinking(source.content);
|
|
10794
|
+
parsed.set(source.filename, operations);
|
|
10795
|
+
}
|
|
10796
|
+
if (!source.operations) return operations;
|
|
10797
|
+
const byKey = new Map(operations.map((operation) => [`${operation.method} ${operation.path}`, operation]));
|
|
10798
|
+
return source.operations.flatMap((reference) => {
|
|
10799
|
+
const [method, ...path] = reference.trim().split(/\s+/);
|
|
10800
|
+
const operation = byKey.get(`${method.toLowerCase()} ${path.join(" ")}`);
|
|
10801
|
+
return operation ? [operation] : [];
|
|
10802
|
+
});
|
|
10803
|
+
}
|
|
10804
|
+
var mountPrefix = (directory) => directory.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10805
|
+
function apiOperationLinkMap(sources, docsConfig, otherMounts = []) {
|
|
10584
10806
|
const map = /* @__PURE__ */ new Map();
|
|
10585
10807
|
const prefixLandings = /* @__PURE__ */ new Map();
|
|
10586
10808
|
const mintlifyPages = /* @__PURE__ */ new Map();
|
|
10587
10809
|
const duplicatePages = [];
|
|
10588
|
-
const
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
const
|
|
10593
|
-
|
|
10810
|
+
const parsedByFile = /* @__PURE__ */ new Map();
|
|
10811
|
+
const bound = apiTabSpecIds(docsConfig.tabs).flatMap(({ tab, specId }) => sources.filter((entry) => tab.api?.source === specAssetPath(entry.filename) && (!entry.tab || entry.tab === tab.tab)).map((source) => ({ source, specId })));
|
|
10812
|
+
const mounts = new Set([...bound.map(({ source }) => source.prefix ? mountPrefix(source.prefix) : ""), ...otherMounts.map(mountPrefix)].filter(Boolean));
|
|
10813
|
+
const underDeeperMount = (route, own2) => {
|
|
10814
|
+
const lower = route.toLowerCase();
|
|
10815
|
+
return [...mounts].some((mount) => mount.length > own2.length && (lower === mount || lower.startsWith(`${mount}/`)));
|
|
10816
|
+
};
|
|
10817
|
+
bound.forEach(({ source, specId }) => {
|
|
10818
|
+
if (!source.prefix) return;
|
|
10819
|
+
const base = source.prefix.replace(/^\/+|\/+$/g, "");
|
|
10820
|
+
const prefix = mountPrefix(source.prefix);
|
|
10594
10821
|
if (prefix) prefixLandings.set(prefix, specId === "default" ? "/api" : `/api/${specId}`);
|
|
10595
|
-
for (const operation of
|
|
10822
|
+
for (const operation of servedOperations(source, parsedByFile)) {
|
|
10596
10823
|
const thallyHref = `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
|
|
10597
10824
|
const leafCandidates = /* @__PURE__ */ new Set();
|
|
10598
10825
|
if (operation.operationId) {
|
|
@@ -10608,31 +10835,66 @@ function apiOperationLinkMap(sources, docsConfig) {
|
|
|
10608
10835
|
const tagSegment = mintlifyOperationSlugSegment(operation.tag ?? "default");
|
|
10609
10836
|
const mintlifyTag = mintlifyUrlSlug(operation.tag ?? "default");
|
|
10610
10837
|
const mintlifyLeaf = mintlifyUrlSlug(operation.summary?.trim() || operation.operationId?.trim() || `${operation.method} ${operation.path}`);
|
|
10611
|
-
const canonical2 = `${
|
|
10612
|
-
if (mintlifyTag && mintlifyLeaf) {
|
|
10838
|
+
const canonical2 = `${base}/${mintlifyTag}/${mintlifyLeaf}`.replace(/^\/+|\/+$/g, "");
|
|
10839
|
+
if (mintlifyTag && mintlifyLeaf && !underDeeperMount(canonical2, prefix)) {
|
|
10613
10840
|
if (!mintlifyPages.has(canonical2)) mintlifyPages.set(canonical2, thallyHref);
|
|
10614
10841
|
else if (mintlifyPages.get(canonical2) !== thallyHref && !duplicatePages.includes(canonical2)) duplicatePages.push(canonical2);
|
|
10842
|
+
if (!map.has(canonical2.toLowerCase())) map.set(canonical2.toLowerCase(), thallyHref);
|
|
10843
|
+
}
|
|
10844
|
+
const tagRoot = `${base}/${mintlifyTag}`.replace(/^\/+|\/+$/g, "");
|
|
10845
|
+
if (mintlifyTag && !underDeeperMount(tagRoot, prefix)) {
|
|
10846
|
+
if (!mintlifyPages.has(tagRoot)) mintlifyPages.set(tagRoot, thallyHref);
|
|
10847
|
+
if (!map.has(tagRoot.toLowerCase())) map.set(tagRoot.toLowerCase(), thallyHref);
|
|
10615
10848
|
}
|
|
10616
|
-
if (mintlifyTag && mintlifyLeaf && !map.has(canonical2.toLowerCase())) map.set(canonical2.toLowerCase(), thallyHref);
|
|
10617
|
-
const tagRoot = `${source.prefix}/${mintlifyTag}`.replace(/^\/+|\/+$/g, "");
|
|
10618
|
-
if (mintlifyTag && !mintlifyPages.has(tagRoot)) mintlifyPages.set(tagRoot, thallyHref);
|
|
10619
|
-
if (mintlifyTag && !map.has(tagRoot.toLowerCase())) map.set(tagRoot.toLowerCase(), thallyHref);
|
|
10620
10849
|
for (const leaf of leafCandidates) {
|
|
10621
10850
|
if (!leaf) continue;
|
|
10622
|
-
const tagged = `${
|
|
10623
|
-
const flat = `${
|
|
10624
|
-
if (!map.has(tagged)) map.set(tagged, thallyHref);
|
|
10625
|
-
if (!map.has(flat)) map.set(flat, thallyHref);
|
|
10851
|
+
const tagged = `${base}/${tagSegment}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10852
|
+
const flat = `${base}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10853
|
+
if (!map.has(tagged) && !underDeeperMount(tagged, prefix)) map.set(tagged, thallyHref);
|
|
10854
|
+
if (!map.has(flat) && !underDeeperMount(flat, prefix)) map.set(flat, thallyHref);
|
|
10626
10855
|
}
|
|
10627
10856
|
}
|
|
10628
10857
|
});
|
|
10629
10858
|
return { operationLinks: map, prefixLandings, mintlifyPages, duplicatePages };
|
|
10630
10859
|
}
|
|
10631
|
-
function
|
|
10860
|
+
function mintlifyEndpointRedirects(maps, pages, existing, warnings) {
|
|
10861
|
+
const { mintlifyPages, duplicatePages } = maps;
|
|
10862
|
+
if (duplicatePages.length > 0) {
|
|
10863
|
+
const listed = duplicatePages.slice(0, 10).map((url) => `/${url}`).join(", ");
|
|
10864
|
+
warnings.push({
|
|
10865
|
+
code: "unsupported-config",
|
|
10866
|
+
message: `${duplicatePages.length} Mintlify endpoint URL${duplicatePages.length === 1 ? " is" : "s are"} shared by more than one operation (same tag and summary), so only the first operation is redirected from each: ${listed}${duplicatePages.length > 10 ? ", ..." : ""}.`
|
|
10867
|
+
});
|
|
10868
|
+
}
|
|
10869
|
+
const taken = servedRoutes(pages);
|
|
10870
|
+
for (const redirect of existing) taken.add(literalRoute(redirect.source) ?? redirect.source);
|
|
10871
|
+
const candidates = [...mintlifyPages].filter(([source]) => !taken.has(literalRoute(`/${source}`) ?? `/${source}`));
|
|
10872
|
+
const redirects = candidates.slice(0, MAX_ENDPOINT_REDIRECTS).map(([source, destination]) => ({ source: `/${source}`, destination }));
|
|
10873
|
+
const capped = candidates.length - redirects.length;
|
|
10874
|
+
if (capped > 0) {
|
|
10875
|
+
warnings.push({
|
|
10876
|
+
code: "unsupported-config",
|
|
10877
|
+
message: `Created ${redirects.length} of ${candidates.length} Mintlify endpoint redirects; skipped ${capped} because a site is limited to ${MAX_ENDPOINT_REDIRECTS} generated endpoint redirects. Old links to skipped endpoint URLs will not redirect.`
|
|
10878
|
+
});
|
|
10879
|
+
}
|
|
10880
|
+
return redirects;
|
|
10881
|
+
}
|
|
10882
|
+
function linkApiMounts(sources, docsConfig, pages, warnings, reservedMounts = []) {
|
|
10883
|
+
const maps = apiOperationLinkMap(sources, docsConfig, reservedMounts);
|
|
10884
|
+
rewriteApiLinksInPages(pages, maps.operationLinks, maps.prefixLandings, warnings, { mounts: reservedMounts, redirects: docsConfig.redirects });
|
|
10885
|
+
return mintlifyEndpointRedirects(maps, pages, docsConfig.redirects ?? [], warnings);
|
|
10886
|
+
}
|
|
10887
|
+
function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings, answered = {}) {
|
|
10632
10888
|
if (operationLinks.size === 0 && prefixLandings.size === 0) return;
|
|
10633
10889
|
const unmatched = /* @__PURE__ */ new Set();
|
|
10890
|
+
const served = servedRoutes(pages);
|
|
10891
|
+
for (const { source } of answered.redirects ?? []) {
|
|
10892
|
+
const route = literalRoute(source);
|
|
10893
|
+
if (route) served.add(route);
|
|
10894
|
+
}
|
|
10895
|
+
const others = (answered.mounts ?? []).map(mountPrefix).filter(Boolean);
|
|
10634
10896
|
for (const page of pages) {
|
|
10635
|
-
page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, unmatched);
|
|
10897
|
+
page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, served, unmatched, others);
|
|
10636
10898
|
}
|
|
10637
10899
|
if (unmatched.size === 0) return;
|
|
10638
10900
|
const sorted = [...unmatched].sort();
|
|
@@ -10641,7 +10903,7 @@ function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings)
|
|
|
10641
10903
|
message: `${sorted.length} link(s) to API endpoints matched no endpoint in the OpenAPI spec, so they now point to the API section's overview page instead: ${sorted.slice(0, 10).join(", ")}${sorted.length > 10 ? `, and ${sorted.length - 10} more` : ""}. Update those links if you want them to reach a specific endpoint.`
|
|
10642
10904
|
});
|
|
10643
10905
|
}
|
|
10644
|
-
function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
|
|
10906
|
+
function rewriteApiOperationLinks(body, linkMap, prefixLandings, served, unmatched, otherMounts) {
|
|
10645
10907
|
if (linkMap.size === 0 && prefixLandings.size === 0) return body;
|
|
10646
10908
|
const orderedPrefixes = [...prefixLandings.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
10647
10909
|
let codeFence = null;
|
|
@@ -10655,8 +10917,12 @@ function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
|
|
|
10655
10917
|
if (codeFence) return line;
|
|
10656
10918
|
const rewriteTarget = (target2) => {
|
|
10657
10919
|
const suffixIndex = target2.search(/[?#]/);
|
|
10658
|
-
const
|
|
10920
|
+
const literal = suffixIndex >= 0 ? target2.slice(0, suffixIndex) : target2;
|
|
10921
|
+
if (served.has(literalRoute(literal) ?? literal)) return target2;
|
|
10922
|
+
const path = literal.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10659
10923
|
const suffix = suffixIndex >= 0 ? target2.slice(suffixIndex) : "";
|
|
10924
|
+
const owner = [...prefixLandings.keys(), ...otherMounts].filter((mount) => path === mount || path.startsWith(`${mount}/`)).reduce((longest, mount) => longest === null || mount.length > longest.length ? mount : longest, null);
|
|
10925
|
+
if (owner !== null && !prefixLandings.has(owner)) return target2;
|
|
10660
10926
|
const mapped = linkMap.get(path);
|
|
10661
10927
|
if (mapped) return `${mapped}${suffix}`;
|
|
10662
10928
|
for (const [prefix, landing] of orderedPrefixes) {
|
|
@@ -12028,6 +12294,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12028
12294
|
const sourceRefPages = [];
|
|
12029
12295
|
const sourceRefAssets = [];
|
|
12030
12296
|
const remoteApiSpecs = [];
|
|
12297
|
+
const localApiMounts = [];
|
|
12031
12298
|
const referencedAssetPaths = /* @__PURE__ */ new Map();
|
|
12032
12299
|
const addAssetReference = (assetPath, referencingPage) => {
|
|
12033
12300
|
const referrers = referencedAssetPaths.get(assetPath);
|
|
@@ -12994,7 +13261,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12994
13261
|
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.routeSegments.join("/") })),
|
|
12995
13262
|
docsConfig
|
|
12996
13263
|
);
|
|
12997
|
-
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
|
|
13264
|
+
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings, { redirects: docsConfig.redirects });
|
|
12998
13265
|
}
|
|
12999
13266
|
} else if (platform === "mintlify") {
|
|
13000
13267
|
const warnSharedSpec = (path, excluded = []) => {
|
|
@@ -13029,34 +13296,17 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13029
13296
|
spec.generatedNavigation = Boolean(spec.directory) && !publishedSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true));
|
|
13030
13297
|
}
|
|
13031
13298
|
docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
|
|
13032
|
-
|
|
13033
|
-
resolvedSpecs.
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
});
|
|
13043
|
-
}
|
|
13044
|
-
const taken = /* @__PURE__ */ new Set([...pages.map((page) => page.id), ...(docsConfig.redirects ?? []).map((redirect) => redirect.source.replace(/^\//, ""))]);
|
|
13045
|
-
const candidates = [...mintlifyPages].filter(([source]) => !taken.has(source));
|
|
13046
|
-
const endpointRedirects = candidates.slice(0, MAX_ENDPOINT_REDIRECTS).map(([source, destination]) => ({ source: `/${source}`, destination }));
|
|
13047
|
-
const alreadyTaken = mintlifyPages.size - candidates.length;
|
|
13048
|
-
const capped = candidates.length - endpointRedirects.length;
|
|
13049
|
-
if (alreadyTaken + capped > 0) {
|
|
13050
|
-
const reasons = [
|
|
13051
|
-
alreadyTaken > 0 ? `${alreadyTaken} because the URL already has a page or redirect` : "",
|
|
13052
|
-
capped > 0 ? `${capped} because a site is limited to ${MAX_ENDPOINT_REDIRECTS} generated endpoint redirects` : ""
|
|
13053
|
-
].filter(Boolean).join(" and ");
|
|
13054
|
-
warnings.push({
|
|
13055
|
-
code: "unsupported-config",
|
|
13056
|
-
message: `Created ${endpointRedirects.length} of ${mintlifyPages.size} Mintlify endpoint redirects; skipped ${alreadyTaken + capped}: ${reasons}. Old links to skipped endpoint URLs will not redirect.`
|
|
13057
|
-
});
|
|
13299
|
+
if (remoteApiSpecs.length > 0) {
|
|
13300
|
+
localApiMounts.push(...resolvedSpecs.flatMap((spec) => spec.directory ? [{ filename: spec.filename, directory: spec.directory, ...spec.tabLabel ? { tab: spec.tabLabel } : {} }] : []));
|
|
13301
|
+
} else {
|
|
13302
|
+
const endpointRedirects = linkApiMounts(
|
|
13303
|
+
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.directory, ...spec.tabLabel ? { tab: spec.tabLabel } : {} })),
|
|
13304
|
+
docsConfig,
|
|
13305
|
+
pages,
|
|
13306
|
+
warnings
|
|
13307
|
+
);
|
|
13308
|
+
if (endpointRedirects.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...endpointRedirects] };
|
|
13058
13309
|
}
|
|
13059
|
-
if (endpointRedirects.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...endpointRedirects] };
|
|
13060
13310
|
} else {
|
|
13061
13311
|
const fallback = findOpenApi(files);
|
|
13062
13312
|
const fallbackContent = fallback ? readSpecFile(fallback.absolutePath, fallback.relativePath, warnings) : void 0;
|
|
@@ -13099,8 +13349,9 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13099
13349
|
if (docsConfig.tabs.some((tab) => tab.api)) docsConfig = { ...docsConfig, api: { ...docsConfig.api, specLink: false } };
|
|
13100
13350
|
}
|
|
13101
13351
|
if (platform === "mintlify") {
|
|
13102
|
-
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source));
|
|
13103
|
-
const
|
|
13352
|
+
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source.toLowerCase()));
|
|
13353
|
+
const served = servedRoutes(pages);
|
|
13354
|
+
const aliases = routeAliases.filter((redirect) => !sources.has(redirect.source.toLowerCase()) && !served.has(literalRoute(redirect.source) ?? redirect.source));
|
|
13104
13355
|
if (aliases.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...aliases] };
|
|
13105
13356
|
docsConfig = addMintlifyHomepageRedirects(addMintlifyDirectoryRedirects(docsConfig, pages), pages);
|
|
13106
13357
|
const reachablePaths = /* @__PURE__ */ new Set([
|
|
@@ -13314,26 +13565,7 @@ ${body}`;
|
|
|
13314
13565
|
}
|
|
13315
13566
|
}
|
|
13316
13567
|
if (docsConfig.redirects) {
|
|
13317
|
-
docsConfig =
|
|
13318
|
-
...docsConfig,
|
|
13319
|
-
redirects: docsConfig.redirects.filter((redirect) => {
|
|
13320
|
-
const literalRoute = (route) => route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route.replace(/\/+$/, "") || "/" : null;
|
|
13321
|
-
const sourceRoute = literalRoute(redirect.source);
|
|
13322
|
-
if (sourceRoute && sourceRoute === literalRoute(redirect.destination)) {
|
|
13323
|
-
warnings.push({
|
|
13324
|
-
code: "unsupported-config",
|
|
13325
|
-
message: `Self-redirect from ${redirect.source} to ${redirect.destination} was omitted.`
|
|
13326
|
-
});
|
|
13327
|
-
return false;
|
|
13328
|
-
}
|
|
13329
|
-
if (!/[+?()[\]{}]/.test(redirect.source)) return true;
|
|
13330
|
-
warnings.push({
|
|
13331
|
-
code: "unsupported-config",
|
|
13332
|
-
message: `Redirect from ${redirect.source} contains route-pattern syntax Next.js cannot express literally and was omitted.`
|
|
13333
|
-
});
|
|
13334
|
-
return false;
|
|
13335
|
-
})
|
|
13336
|
-
};
|
|
13568
|
+
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), [...pages, ...sourceRefPages], warnings);
|
|
13337
13569
|
}
|
|
13338
13570
|
if (pages.length === 0) {
|
|
13339
13571
|
const reasons = warnings.map((warning) => warning.message);
|
|
@@ -13546,6 +13778,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13546
13778
|
assets,
|
|
13547
13779
|
...importedSourceRefs.length > 0 ? { sourceRefs: importedSourceRefs } : {},
|
|
13548
13780
|
...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
|
|
13781
|
+
...remoteApiSpecs.length > 0 && localApiMounts.length > 0 ? { localApiMounts } : {},
|
|
13549
13782
|
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
13550
13783
|
...quarantinedFiles.length > 0 ? { quarantinedFiles } : {},
|
|
13551
13784
|
...droppedGatedFiles.length > 0 ? { droppedGatedPages: droppedGatedFiles.length } : {},
|
|
@@ -14358,7 +14591,7 @@ function mergeSourceNavigation(existing, incoming) {
|
|
|
14358
14591
|
}
|
|
14359
14592
|
|
|
14360
14593
|
// src/url-run.ts
|
|
14361
|
-
var CONVERTER_VERSION = "url-evidence-
|
|
14594
|
+
var CONVERTER_VERSION = "url-evidence-v4";
|
|
14362
14595
|
var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
|
|
14363
14596
|
var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
|
|
14364
14597
|
var MAX_RESPONSE_BYTES = 8e6;
|
|
@@ -14617,21 +14850,22 @@ function resolveMigrationRecord(records, record) {
|
|
|
14617
14850
|
}
|
|
14618
14851
|
return record;
|
|
14619
14852
|
}
|
|
14620
|
-
function navigationConfig(
|
|
14853
|
+
function navigationConfig(checkpoint, records) {
|
|
14854
|
+
const navigation = checkpoint.navigation;
|
|
14855
|
+
const projection = localeProjection(checkpoint);
|
|
14621
14856
|
const project2 = (nodes) => nodes.flatMap((node) => {
|
|
14622
14857
|
const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
|
|
14623
14858
|
const target2 = resolveMigrationRecord(records, record);
|
|
14624
14859
|
const page = target2?.page;
|
|
14625
14860
|
const children = project2(node.children ?? []);
|
|
14626
14861
|
if (children.length) {
|
|
14627
|
-
const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === page
|
|
14628
|
-
return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [
|
|
14862
|
+
const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
|
|
14863
|
+
return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [projection.navigationId(page)] : [], ...children] }];
|
|
14629
14864
|
}
|
|
14630
|
-
return page ? [
|
|
14865
|
+
return page ? [projection.navigationId(page)] : [];
|
|
14631
14866
|
});
|
|
14632
14867
|
const tabs = navigation.filter((node) => node.kind === "tab");
|
|
14633
14868
|
if (tabs.length) {
|
|
14634
|
-
const projection = localeProjection({ records: [...records.values()] });
|
|
14635
14869
|
const locales = [...new Set(tabs.map((tab) => tab.locale ?? projection.defaultLocale))];
|
|
14636
14870
|
const localizedTabs = (locale) => tabs.filter((tab) => (tab.locale ?? projection.defaultLocale) === locale).flatMap((tab) => {
|
|
14637
14871
|
const pages2 = project2(tab.children ?? []);
|
|
@@ -14649,16 +14883,16 @@ function navigationConfig(navigation, records) {
|
|
|
14649
14883
|
const pages = project2(navigation);
|
|
14650
14884
|
return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
|
|
14651
14885
|
}
|
|
14886
|
+
function canonicalLanguage(value) {
|
|
14887
|
+
if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
|
|
14888
|
+
try {
|
|
14889
|
+
return Intl.getCanonicalLocales(value)[0];
|
|
14890
|
+
} catch {
|
|
14891
|
+
return void 0;
|
|
14892
|
+
}
|
|
14893
|
+
}
|
|
14652
14894
|
function observedLocale(document, id, scopePath) {
|
|
14653
14895
|
const issues = [];
|
|
14654
|
-
const canonicalLanguage = (value) => {
|
|
14655
|
-
if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
|
|
14656
|
-
try {
|
|
14657
|
-
return Intl.getCanonicalLocales(value)[0];
|
|
14658
|
-
} catch {
|
|
14659
|
-
return void 0;
|
|
14660
|
-
}
|
|
14661
|
-
};
|
|
14662
14896
|
const candidates = [];
|
|
14663
14897
|
if (document.locale) {
|
|
14664
14898
|
const locale = canonicalLanguage(document.locale);
|
|
@@ -14680,25 +14914,60 @@ function observedLocale(document, id, scopePath) {
|
|
|
14680
14914
|
}
|
|
14681
14915
|
function localeProjection(checkpoint) {
|
|
14682
14916
|
const pages = checkpoint.records.flatMap((record) => record.page ? [record.page] : []);
|
|
14683
|
-
const
|
|
14917
|
+
const declaredLocale = (page) => {
|
|
14684
14918
|
try {
|
|
14685
14919
|
return page.locale ? Intl.getCanonicalLocales(page.locale)[0] : void 0;
|
|
14686
14920
|
} catch {
|
|
14687
14921
|
return void 0;
|
|
14688
14922
|
}
|
|
14689
14923
|
};
|
|
14924
|
+
const declaredLocales = [...new Set(pages.map(declaredLocale).filter((locale) => Boolean(locale)))];
|
|
14925
|
+
const defaultLocale = pages.some((page) => !page.locale) || declaredLocales.includes("en") ? "en" : declaredLocales[0] ?? "en";
|
|
14926
|
+
const records = new Map(checkpoint.records.map((record) => [record.url, record]));
|
|
14927
|
+
const memberships = /* @__PURE__ */ new Map();
|
|
14928
|
+
const observe = (nodes, parentLocale) => {
|
|
14929
|
+
for (const node of nodes) {
|
|
14930
|
+
const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
|
|
14931
|
+
const entry = node.url ? records.get(canonical(node.url, checkpoint.sourceUrl) ?? "") : void 0;
|
|
14932
|
+
const page = resolveMigrationRecord(records, entry)?.page;
|
|
14933
|
+
if (page && locale) {
|
|
14934
|
+
const languages = memberships.get(page) ?? /* @__PURE__ */ new Set();
|
|
14935
|
+
languages.add(locale);
|
|
14936
|
+
memberships.set(page, languages);
|
|
14937
|
+
}
|
|
14938
|
+
observe(node.children ?? [], locale);
|
|
14939
|
+
}
|
|
14940
|
+
};
|
|
14941
|
+
observe(checkpoint.navigation);
|
|
14942
|
+
const primaryIds = new Set(pages.filter((page) => !declaredLocale(page) || declaredLocale(page) === defaultLocale).filter((page) => memberships.get(page)?.has(defaultLocale)).map((page) => page.navigationId));
|
|
14943
|
+
const remapped = /* @__PURE__ */ new Map();
|
|
14944
|
+
for (const page of pages) {
|
|
14945
|
+
const languages = memberships.get(page);
|
|
14946
|
+
if (languages?.size !== 1) continue;
|
|
14947
|
+
const locale = [...languages][0];
|
|
14948
|
+
const prefix = `${locale.toLowerCase()}-`;
|
|
14949
|
+
if (locale === defaultLocale || !page.navigationId.toLowerCase().startsWith(prefix)) continue;
|
|
14950
|
+
const navigationId2 = page.navigationId.slice(prefix.length);
|
|
14951
|
+
if (primaryIds.has(navigationId2)) remapped.set(page, { locale, navigationId: navigationId2 });
|
|
14952
|
+
}
|
|
14953
|
+
const canonicalLocale = (page) => remapped.get(page)?.locale ?? declaredLocale(page);
|
|
14954
|
+
const navigationId = (page) => remapped.get(page)?.navigationId ?? page.navigationId;
|
|
14690
14955
|
const locales = [...new Set(pages.map(canonicalLocale).filter((locale) => Boolean(locale)))];
|
|
14691
|
-
const defaultLocale = pages.some((page) => !page.locale) || locales.includes("en") ? "en" : locales[0] ?? "en";
|
|
14692
14956
|
const pageId = (page) => {
|
|
14693
14957
|
const locale = canonicalLocale(page);
|
|
14694
|
-
return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${page
|
|
14958
|
+
return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${navigationId(page)}` : page.id;
|
|
14695
14959
|
};
|
|
14696
14960
|
const route = (page) => {
|
|
14697
14961
|
const locale = canonicalLocale(page);
|
|
14698
14962
|
const prefix = locale && locale !== defaultLocale ? `/${locale}` : "";
|
|
14699
|
-
return `${prefix}${page
|
|
14963
|
+
return `${prefix}${navigationId(page) === "introduction" ? "/" : `/${navigationId(page)}`}`;
|
|
14700
14964
|
};
|
|
14701
|
-
|
|
14965
|
+
const redirects = checkpoint.records.flatMap((record) => {
|
|
14966
|
+
if (!record.page || !remapped.has(record.page)) return [];
|
|
14967
|
+
const source = new URL(record.url);
|
|
14968
|
+
return source.search || !/^\/[A-Za-z0-9/_~.%+-]*$/.test(source.pathname) ? [] : [{ source: source.pathname, destination: route(record.page), permanent: true }];
|
|
14969
|
+
});
|
|
14970
|
+
return { canonicalLocale, navigationId, locales, defaultLocale, pageId, route, redirects };
|
|
14702
14971
|
}
|
|
14703
14972
|
function validateUrlMigration(checkpoint, cancelled = false) {
|
|
14704
14973
|
const issues = [];
|
|
@@ -14707,7 +14976,7 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
14707
14976
|
const records = new Map(checkpoint.records.map((record) => [record.url, record]));
|
|
14708
14977
|
const source = new URL(checkpoint.sourceUrl);
|
|
14709
14978
|
const projection = localeProjection(checkpoint);
|
|
14710
|
-
const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!record.page
|
|
14979
|
+
const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!projection.canonicalLocale(record.page) || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [projection.navigationId(record.page)] : []));
|
|
14711
14980
|
const outputIds = /* @__PURE__ */ new Set();
|
|
14712
14981
|
const resolve4 = (url) => {
|
|
14713
14982
|
const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
|
|
@@ -14721,8 +14990,8 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
14721
14990
|
const outputId = projection.pageId(record.page);
|
|
14722
14991
|
if (outputIds.has(outputId)) issues.push({ code: "collision", source: record.url, message: `Multiple source pages map to runtime path ${outputId}.` });
|
|
14723
14992
|
outputIds.add(outputId);
|
|
14724
|
-
if (record.page
|
|
14725
|
-
issues.push({ code: "missing-page", source: record.url, message: `Secondary locale ${record.page
|
|
14993
|
+
if (projection.canonicalLocale(record.page) && projection.canonicalLocale(record.page) !== projection.defaultLocale && !primaryIds.has(projection.navigationId(record.page))) {
|
|
14994
|
+
issues.push({ code: "missing-page", source: record.url, message: `Secondary locale ${projection.canonicalLocale(record.page)} requires a primary ${projection.defaultLocale} page at ${projection.navigationId(record.page)}; the runtime cannot serve an orphan translation.` });
|
|
14726
14995
|
}
|
|
14727
14996
|
const actual = markdownCode(record.page.body);
|
|
14728
14997
|
if (JSON.stringify(actual) !== JSON.stringify(record.evidence.code)) issues.push({ code: "code-mismatch", source: record.url, message: "Source code payloads differ from converted code payloads." });
|
|
@@ -14756,13 +15025,20 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
14756
15025
|
}
|
|
14757
15026
|
}
|
|
14758
15027
|
}
|
|
14759
|
-
const visit = (nodes) => {
|
|
15028
|
+
const visit = (nodes, parentLocale) => {
|
|
14760
15029
|
for (const node of nodes) {
|
|
15030
|
+
const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
|
|
14761
15031
|
if (node.url) {
|
|
14762
15032
|
const url = new URL(node.url, source);
|
|
14763
|
-
if (inScope(url, source, checkpoint.scopePath)
|
|
15033
|
+
if (inScope(url, source, checkpoint.scopePath)) {
|
|
15034
|
+
const page = resolve4(url.toString())?.page;
|
|
15035
|
+
if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
|
|
15036
|
+
else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
|
|
15037
|
+
issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
|
|
15038
|
+
}
|
|
15039
|
+
}
|
|
14764
15040
|
}
|
|
14765
|
-
visit(node.children ?? []);
|
|
15041
|
+
visit(node.children ?? [], locale);
|
|
14766
15042
|
}
|
|
14767
15043
|
};
|
|
14768
15044
|
visit(checkpoint.navigation);
|
|
@@ -15180,7 +15456,7 @@ ${difference}`;
|
|
|
15180
15456
|
}
|
|
15181
15457
|
};
|
|
15182
15458
|
labelPages(checkpoint.navigation);
|
|
15183
|
-
const pages = checkpoint.records.flatMap((record) => record.page ? [{ ...record.page, id: projection.pageId(record.page), ...record.page
|
|
15459
|
+
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) => {
|
|
15184
15460
|
let url;
|
|
15185
15461
|
try {
|
|
15186
15462
|
url = new URL(value, record.evidence.finalUrl);
|
|
@@ -15210,11 +15486,12 @@ ${difference}`;
|
|
|
15210
15486
|
platform: options.platform ?? "generic",
|
|
15211
15487
|
pages,
|
|
15212
15488
|
assets: [...assets.values()],
|
|
15213
|
-
docsConfig: navigationConfig(checkpoint
|
|
15489
|
+
docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
|
|
15214
15490
|
warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
|
|
15215
15491
|
urlMigrationValidation: validation,
|
|
15216
15492
|
stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
|
|
15217
15493
|
};
|
|
15494
|
+
if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
|
|
15218
15495
|
if (projection.locales.length) {
|
|
15219
15496
|
const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
|
|
15220
15497
|
bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };
|
|
@@ -15270,7 +15547,6 @@ import { createHash as createHash4 } from "crypto";
|
|
|
15270
15547
|
import { lookup } from "dns/promises";
|
|
15271
15548
|
import { request } from "https";
|
|
15272
15549
|
import { isIP } from "net";
|
|
15273
|
-
import { parse as parseYaml7 } from "yaml";
|
|
15274
15550
|
var MAX_SPEC_BYTES2 = 25e6;
|
|
15275
15551
|
var MAX_REDIRECTS = 3;
|
|
15276
15552
|
function isPublicAddress(address, family) {
|
|
@@ -15364,9 +15640,7 @@ async function downloadSpec(value, fetcher) {
|
|
|
15364
15640
|
}
|
|
15365
15641
|
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
15366
15642
|
}
|
|
15367
|
-
function parseOpenApi(
|
|
15368
|
-
const text3 = content.toString("utf8");
|
|
15369
|
-
const parsed = extension === "yaml" || !text3.trimStart().startsWith("{") ? parseYaml7(text3) : JSON.parse(text3);
|
|
15643
|
+
function parseOpenApi(parsed) {
|
|
15370
15644
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
15371
15645
|
const document = parsed;
|
|
15372
15646
|
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
@@ -15379,10 +15653,11 @@ function parseOpenApi(content, extension) {
|
|
|
15379
15653
|
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
15380
15654
|
const operations = [];
|
|
15381
15655
|
for (const [path, value] of paths) {
|
|
15382
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
15656
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || isWithheld(value)) continue;
|
|
15383
15657
|
for (const [method, raw] of Object.entries(value)) {
|
|
15384
15658
|
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
15385
15659
|
const operation = raw;
|
|
15660
|
+
if (isWithheld(operation)) continue;
|
|
15386
15661
|
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
15387
15662
|
operations.push({
|
|
15388
15663
|
method: method.toLowerCase(),
|
|
@@ -15397,9 +15672,8 @@ function parseOpenApi(content, extension) {
|
|
|
15397
15672
|
function slug(value) {
|
|
15398
15673
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
15399
15674
|
}
|
|
15400
|
-
function operationTarget(operation) {
|
|
15401
|
-
|
|
15402
|
-
return `/api/default/${[...path, operation.method].join("/")}`;
|
|
15675
|
+
function operationTarget(specId, operation) {
|
|
15676
|
+
return `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
|
|
15403
15677
|
}
|
|
15404
15678
|
async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
15405
15679
|
if (!bundle.remoteApiSpecs?.length) return bundle;
|
|
@@ -15409,28 +15683,32 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15409
15683
|
const warnings = [...bundle.warnings];
|
|
15410
15684
|
const pages = bundle.pages.map((page) => ({ ...page }));
|
|
15411
15685
|
const authoredLinks = /* @__PURE__ */ new Set();
|
|
15686
|
+
const served = servedRoutes(pages);
|
|
15687
|
+
const endpointUrls = /* @__PURE__ */ new Map();
|
|
15412
15688
|
for (const page of bundle.pages) {
|
|
15413
15689
|
for (const match of page.body.matchAll(/\/[a-z0-9_/-]+/gi)) {
|
|
15414
15690
|
const segments = match[0].split("/").filter(Boolean);
|
|
15415
15691
|
if (segments.length < 3) continue;
|
|
15416
15692
|
const section = segments[0].toLowerCase();
|
|
15417
|
-
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0]);
|
|
15693
|
+
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0].replace(/\/+$/, ""));
|
|
15418
15694
|
}
|
|
15419
15695
|
}
|
|
15696
|
+
const bound = [];
|
|
15420
15697
|
for (const reference of bundle.remoteApiSpecs) {
|
|
15421
15698
|
for (let index = warnings.length - 1; index >= 0; index--) {
|
|
15422
15699
|
if (warnings[index].source === reference.url && warnings[index].message.includes("requires a network download")) warnings.splice(index, 1);
|
|
15423
15700
|
}
|
|
15424
15701
|
try {
|
|
15425
|
-
const
|
|
15426
|
-
const extension = /\.ya?ml$/i.test(url.pathname) ? "yaml" : "json";
|
|
15427
|
-
const downloaded = await downloadSpec(reference.url, fetcher);
|
|
15702
|
+
const downloaded = withoutByteOrderMark(await downloadSpec(reference.url, fetcher));
|
|
15428
15703
|
const pruned = markExcluded(downloaded, reference.withheldOperations ?? [], reference.keptOperations);
|
|
15429
15704
|
const body = pruned.content;
|
|
15430
|
-
if (reference.withheldOperations?.length
|
|
15431
|
-
|
|
15705
|
+
if (reference.withheldOperations?.length && !warnings.some((warning) => warning.code === "gated-page" && warning.source === reference.url)) {
|
|
15706
|
+
warnings.push({ code: "gated-page", message: sharedSpecMessage(reference.url, pruned.excluded), source: reference.url });
|
|
15707
|
+
}
|
|
15708
|
+
const { format: extension, document } = parseSpecDocument(body.toString("utf8"));
|
|
15709
|
+
const operations = parseOpenApi(document);
|
|
15432
15710
|
const filename = `openapi-${createHash4("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
15433
|
-
assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
15711
|
+
if (!assets.some((asset) => asset.path === `openapi/${filename}`)) assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
15434
15712
|
for (const page of pages) {
|
|
15435
15713
|
const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
|
|
15436
15714
|
if (ref?.specRef === reference.url) page.openapi = withSpecRef(ref, `openapi/${filename}`);
|
|
@@ -15438,21 +15716,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15438
15716
|
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
15439
15717
|
if (tab) tab.api = { source: `openapi/${filename}` };
|
|
15440
15718
|
else insertApiTab(tabs, { tab: reference.tabLabel ?? "API Reference", ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {}, api: { source: `openapi/${filename}` } }, reference.parentTab);
|
|
15441
|
-
|
|
15442
|
-
for (const operation of operations) {
|
|
15443
|
-
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
15444
|
-
if (routes.has(key)) routes.set(key, null);
|
|
15445
|
-
else routes.set(key, operationTarget(operation));
|
|
15446
|
-
}
|
|
15447
|
-
const existingSources = new Set(redirects.map((redirect) => redirect.source));
|
|
15448
|
-
for (const source of authoredLinks) {
|
|
15449
|
-
const parts = source.split("/").filter(Boolean);
|
|
15450
|
-
const key = parts.slice(-2).join("/");
|
|
15451
|
-
const destination = routes.get(key);
|
|
15452
|
-
if (!destination || existingSources.has(source)) continue;
|
|
15453
|
-
redirects.push({ source, destination, permanent: false });
|
|
15454
|
-
existingSources.add(source);
|
|
15455
|
-
}
|
|
15719
|
+
bound.push({ reference, filename, content: body, operations, tabName: tab?.tab ?? reference.tabLabel ?? "API Reference" });
|
|
15456
15720
|
} catch (error) {
|
|
15457
15721
|
warnings.push({
|
|
15458
15722
|
code: "fetch-failed",
|
|
@@ -15461,11 +15725,59 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15461
15725
|
});
|
|
15462
15726
|
}
|
|
15463
15727
|
}
|
|
15728
|
+
const directories = [
|
|
15729
|
+
...(bundle.localApiMounts ?? []).map(({ directory }) => directory),
|
|
15730
|
+
...bundle.remoteApiSpecs.flatMap((reference) => (reference.directories ?? []).map(({ directory }) => directory))
|
|
15731
|
+
];
|
|
15732
|
+
const endpointRedirects = linkApiMounts(
|
|
15733
|
+
[
|
|
15734
|
+
...(bundle.localApiMounts ?? []).map(({ filename, directory, tab }) => ({
|
|
15735
|
+
filename,
|
|
15736
|
+
content: Buffer.from(assets.find((asset) => asset.path === `openapi/${filename}`)?.content ?? []),
|
|
15737
|
+
prefix: directory,
|
|
15738
|
+
...tab ? { tab } : {}
|
|
15739
|
+
})),
|
|
15740
|
+
...bound.flatMap(({ reference, filename, content, tabName }) => (reference.directories ?? []).map(({ directory, operations: listed }) => ({ filename, content, prefix: directory, tab: tabName, ...listed ? { operations: listed } : {} })))
|
|
15741
|
+
],
|
|
15742
|
+
{ ...bundle.docsConfig, tabs, redirects },
|
|
15743
|
+
pages,
|
|
15744
|
+
warnings,
|
|
15745
|
+
directories
|
|
15746
|
+
);
|
|
15747
|
+
for (const redirect of endpointRedirects) {
|
|
15748
|
+
redirects.push(redirect);
|
|
15749
|
+
endpointUrls.set(literalRoute(redirect.source) ?? redirect.source, redirect.destination);
|
|
15750
|
+
}
|
|
15751
|
+
const existingSources = new Set(redirects.map((redirect) => literalRoute(redirect.source) ?? redirect.source.toLowerCase()));
|
|
15752
|
+
const mounts = directories.map(mountPrefix).filter(Boolean);
|
|
15753
|
+
const underMount = (source) => mounts.some((prefix) => source.toLowerCase() === `/${prefix}` || source.toLowerCase().startsWith(`/${prefix}/`));
|
|
15754
|
+
const guesses = /* @__PURE__ */ new Map();
|
|
15755
|
+
const specIds = apiTabSpecIds(tabs);
|
|
15756
|
+
for (const { reference, filename, operations, tabName } of bound) {
|
|
15757
|
+
const specId = specIds.find(({ tab }) => tab.tab === tabName && tab.api?.source === `openapi/${filename}`)?.specId;
|
|
15758
|
+
const listed = reference.directories?.length && reference.directories.every((directory) => directory.operations) ? new Set(reference.directories.flatMap((directory) => directory.operations ?? []).map((name) => name.trim().split(/\s+/)).map(([method, ...path]) => `${method.toLowerCase()} ${path.join(" ")}`)) : null;
|
|
15759
|
+
for (const operation of operations) {
|
|
15760
|
+
if (listed && !listed.has(`${operation.method} ${operation.path}`)) continue;
|
|
15761
|
+
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
15762
|
+
guesses.set(key, guesses.has(key) || !specId ? null : operationTarget(specId, operation));
|
|
15763
|
+
}
|
|
15764
|
+
}
|
|
15765
|
+
for (const source of authoredLinks) {
|
|
15766
|
+
const destination = guesses.get(source.split("/").filter(Boolean).slice(-2).join("/"));
|
|
15767
|
+
if (!destination || existingSources.has(literalRoute(source) ?? source.toLowerCase()) || served.has(literalRoute(source) ?? source) || underMount(source)) continue;
|
|
15768
|
+
redirects.push({ source, destination, permanent: false });
|
|
15769
|
+
existingSources.add(literalRoute(source) ?? source.toLowerCase());
|
|
15770
|
+
}
|
|
15771
|
+
const direct = redirects.map((redirect) => {
|
|
15772
|
+
const endpoint = endpointUrls.get(literalRoute(redirect.destination) ?? "");
|
|
15773
|
+
return endpoint ? { ...redirect, destination: endpoint } : redirect;
|
|
15774
|
+
});
|
|
15464
15775
|
return {
|
|
15465
15776
|
...bundle,
|
|
15466
15777
|
pages,
|
|
15467
15778
|
assets,
|
|
15468
|
-
|
|
15779
|
+
// Redirects added above meet the same checks as any other: none may fail the build, hide a page that exists, or loop.
|
|
15780
|
+
docsConfig: dropConflictingRedirects(dropUnroutableRedirects({ ...bundle.docsConfig, tabs, ...direct.length > 0 ? { redirects: direct } : {} }, warnings), pages, warnings),
|
|
15469
15781
|
warnings
|
|
15470
15782
|
};
|
|
15471
15783
|
}
|