mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-29 20:05:25 +08:00
fix: preserve Mintlify routes and localized navigation
This commit is contained in:
@@ -27,7 +27,7 @@ function decodeXml(value) {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
export function renderAlternateSitemap(sitemap, base = BASE) {
|
||||
export function renderAlternateSitemap(sitemap, base = BASE, explicitGroups = []) {
|
||||
const urls = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => decodeXml(match[1]));
|
||||
const groups = new Map();
|
||||
for (const url of urls) {
|
||||
@@ -42,6 +42,8 @@ export function renderAlternateSitemap(sitemap, base = BASE) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const group of explicitGroups) groups.set(`explicit:${group.en}`, group);
|
||||
|
||||
const shared = [...groups.values()]
|
||||
.filter((group) => LOCALES.every((locale) => group[locale]))
|
||||
.sort((left, right) => left.en.localeCompare(right.en));
|
||||
@@ -66,6 +68,33 @@ export function renderAlternateSitemap(sitemap, base = BASE) {
|
||||
return { xml: `${lines.join("\n")}\n`, groups: shared.length };
|
||||
}
|
||||
|
||||
function legacyOpenApiSlug(value) {
|
||||
return value.replace(/\s+/g, "-").toLowerCase();
|
||||
}
|
||||
|
||||
function encodeRoute(route) {
|
||||
return route.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
||||
}
|
||||
|
||||
async function collectOpenApiAlternateGroups(root = ROOT, base = BASE) {
|
||||
const groups = new Map();
|
||||
for (const locale of LOCALES) {
|
||||
const spec = JSON.parse(await readFile(path.join(root, `openapi/service-api-${locale}.json`), "utf8"));
|
||||
for (const [apiPath, pathItem] of Object.entries(spec.paths ?? {})) {
|
||||
for (const [method, operation] of Object.entries(pathItem)) {
|
||||
if (!/^(?:get|post|put|patch|delete|head|options)$/i.test(method) || !operation?.summary) continue;
|
||||
const tag = operation.tags?.[0] ?? "unknown";
|
||||
const route = encodeRoute(`api-reference/${legacyOpenApiSlug(tag)}/${legacyOpenApiSlug(operation.summary)}`);
|
||||
const key = `${method.toLowerCase()} ${apiPath}`;
|
||||
const group = groups.get(key) ?? {};
|
||||
group[locale] = `${base}/${locale}/${route}`;
|
||||
groups.set(key, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...groups.values()];
|
||||
}
|
||||
|
||||
async function loadSource(source) {
|
||||
if (/^https?:\/\//.test(source)) {
|
||||
const response = await fetch(source, { headers: { "user-agent": "LangBot docs alternate-sitemap generator" } });
|
||||
@@ -77,7 +106,8 @@ async function loadSource(source) {
|
||||
|
||||
async function main() {
|
||||
const source = process.env.SITEMAP_SOURCE ?? path.join(ROOT, "dist/public/sitemap.xml");
|
||||
const { xml, groups } = renderAlternateSitemap(await loadSource(source));
|
||||
const openApiGroups = await collectOpenApiAlternateGroups();
|
||||
const { xml, groups } = renderAlternateSitemap(await loadSource(source), BASE, openApiGroups);
|
||||
const outputs = process.env.SITEMAP_OUTPUT
|
||||
? [path.resolve(process.env.SITEMAP_OUTPUT)]
|
||||
: [path.join(ROOT, "dist/public/sitemap-alternates.xml"), path.join(ROOT, "sitemap-alternates.xml")];
|
||||
|
||||
@@ -115,33 +115,15 @@ function cloudflarePattern(value, destination = false) {
|
||||
return value.replace(/:([A-Za-z][A-Za-z0-9_]*)\*/g, destination ? ":splat" : "*");
|
||||
}
|
||||
|
||||
function slugifyOpenApiSummary(value) {
|
||||
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
export function collectOpenApiRedirectTargets(spec) {
|
||||
const targets = new Map();
|
||||
for (const [apiPath, pathItem] of Object.entries(spec?.paths ?? {})) {
|
||||
for (const [method, operation] of Object.entries(pathItem)) {
|
||||
if (!/^(?:get|post|put|patch|delete|head|options)$/i.test(method) || !operation?.summary) continue;
|
||||
const summarySlug = slugifyOpenApiSummary(operation.summary);
|
||||
const routePath = apiPath.replaceAll("{", "").replaceAll("}", "");
|
||||
targets.set(summarySlug, `/en/api-reference${routePath}/${method.toLowerCase()}`);
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
export function renderCloudflareRedirects(docs, englishOpenApiSpec) {
|
||||
const apiTargets = collectOpenApiRedirectTargets(englishOpenApiSpec);
|
||||
const lines = ["/ /en/insight/guide 302"];
|
||||
export function renderCloudflareRedirects(docs) {
|
||||
const lines = [
|
||||
"/ /en/insight/guide 302",
|
||||
"/zh/develop/adapter/discord /zh/develop/adapter/discord/README 308",
|
||||
"/scripts/README-blog-articles /en/articles 308",
|
||||
];
|
||||
for (const redirect of docs.redirects ?? []) {
|
||||
const status = redirect.permanent === false ? 307 : 308;
|
||||
const destinationSlug = redirect.destination.match(/^\/en\/api-reference\/[^/]+\/([^/:*]+)$/)?.[1];
|
||||
const destination = destinationSlug && apiTargets.get(destinationSlug)
|
||||
? apiTargets.get(destinationSlug)
|
||||
: redirect.destination;
|
||||
lines.push(`${cloudflarePattern(redirect.source)} ${cloudflarePattern(destination, true)} ${status}`);
|
||||
lines.push(`${cloudflarePattern(redirect.source)} ${cloudflarePattern(redirect.destination, true)} ${status}`);
|
||||
}
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
@@ -159,8 +141,6 @@ export async function prepareFumapress({ root = ROOT, outRoot = root } = {}) {
|
||||
if (openapi.length !== LOCALES.length) {
|
||||
throw new Error(`Expected one nested OpenAPI source per locale, found ${openapi.length}`);
|
||||
}
|
||||
const englishOpenApi = JSON.parse(await readFile(path.join(root, openapi.find((item) => item.locale === "en").source), "utf8"));
|
||||
|
||||
const contentRoot = path.join(outRoot, "content");
|
||||
const publicRoot = path.join(outRoot, "public");
|
||||
await rm(contentRoot, { recursive: true, force: true });
|
||||
@@ -200,13 +180,13 @@ export async function prepareFumapress({ root = ROOT, outRoot = root } = {}) {
|
||||
}
|
||||
|
||||
await cp(path.join(root, "robots.txt"), path.join(publicRoot, "robots.txt"), { force: true });
|
||||
await writeFile(path.join(publicRoot, "_redirects"), renderCloudflareRedirects(docs, englishOpenApi));
|
||||
await writeFile(path.join(publicRoot, "_redirects"), renderCloudflareRedirects(docs));
|
||||
|
||||
return {
|
||||
documents: documents.length,
|
||||
fallbackDefaults: 0,
|
||||
localeOnlyDocuments,
|
||||
redirects: (docs.redirects?.length ?? 0) + 1,
|
||||
redirects: (docs.redirects?.length ?? 0) + 3,
|
||||
locales: [...LOCALES],
|
||||
openapi,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user