deer-flow/frontend/tests/unit/content/docs-links.test.ts
urzeye cd86275638
fix(docs): repair broken documentation links (#4330)
* fix(docs): repair broken documentation links

* revert(docs): restore ACP protocol naming
2026-07-22 07:32:05 +08:00

90 lines
2.9 KiB
TypeScript

import { readFileSync, readdirSync } from "node:fs";
import { join, relative, sep } from "node:path";
import { describe, expect, it } from "@rstest/core";
const CONTENT_ROOT = join(process.cwd(), "src/content");
const DOC_LANGUAGES = ["en", "zh"] as const;
const DOCS_ORIGIN = "https://docs.example";
const LINK_PATTERN =
/(?<!!)\[[^\]]*\]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"']*["'])?\s*\)|\bhref\s*=\s*(?:\{\s*)?["']([^"']+)["'](?:\s*\})?/g;
const UNLOCALIZED_DOCS_PATH = /^\/docs(?=\/|[?#]|$)/;
function findMdxFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
return entry.isDirectory()
? findMdxFiles(path)
: entry.name.endsWith(".mdx")
? [path]
: [];
});
}
function routeForMdx(path: string, lang: string): string {
const localeRoot = join(CONTENT_ROOT, lang);
const relativePath = relative(localeRoot, path).split(sep).join("/");
const pagePath = relativePath
.replace(/\.mdx$/, "")
.replace(/(?:^|\/)index$/, "");
return `/${lang}/docs${pagePath ? `/${pagePath}` : ""}`;
}
function extractLinks(source: string): Array<{ href: string; line: number }> {
return [...source.matchAll(LINK_PATTERN)].map((match) => ({
href: match[1] ?? match[2] ?? match[3] ?? "",
line: source.slice(0, match.index).split("\n").length,
}));
}
function resolveDocsPath(
href: string,
lang: string,
sourceRoute: string,
): string | undefined {
const localizedHref = UNLOCALIZED_DOCS_PATH.test(href)
? `/${lang}${href}`
: href;
const url = new URL(localizedHref, `${DOCS_ORIGIN}${sourceRoute}`);
if (
url.origin !== DOCS_ORIGIN ||
!/^\/(?:en|zh)\/docs(?:\/|$)/.test(url.pathname)
) {
return undefined;
}
return url.pathname.replace(/\/$/, "");
}
describe("documentation content links", () => {
it("only links to documentation routes backed by MDX pages", () => {
const mdxFiles = DOC_LANGUAGES.flatMap((lang) =>
findMdxFiles(join(CONTENT_ROOT, lang)),
);
const routes = new Set(
DOC_LANGUAGES.flatMap((lang) =>
mdxFiles
.filter((path) => path.startsWith(join(CONTENT_ROOT, lang)))
.map((path) => routeForMdx(path, lang)),
),
);
const brokenLinks = DOC_LANGUAGES.flatMap((lang) =>
findMdxFiles(join(CONTENT_ROOT, lang)).flatMap((path) => {
const source = readFileSync(path, "utf8");
const sourceRoute = routeForMdx(path, lang);
return extractLinks(source).flatMap(({ href, line }) => {
const targetRoute = resolveDocsPath(href, lang, sourceRoute);
if (!targetRoute || routes.has(targetRoute)) {
return [];
}
return [
`${relative(CONTENT_ROOT, path).split(sep).join("/")}:${line} -> ${href} (${targetRoute})`,
];
});
}),
);
expect(brokenLinks).toEqual([]);
});
});