diff --git a/scripts/lib/docs-facts.mjs b/scripts/lib/docs-facts.mjs index c9c46d594..26d9a4ac3 100644 --- a/scripts/lib/docs-facts.mjs +++ b/scripts/lib/docs-facts.mjs @@ -1,7 +1,7 @@ import { readFile, readdir, stat } from "node:fs/promises"; import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; -const readmeLinkTargets = (content) => +const readmeLinkTargets = (content, language = "en") => [...content.matchAll(/\]\(([^)\s]+)\)/gu)] .map((match) => match[1] ?? "") .filter( @@ -9,6 +9,18 @@ const readmeLinkTargets = (content) => !target.startsWith("#") && !/^README(?:_[A-Za-z0-9-]+)?\.md$/u.test(target), ) + .map((target) => { + if (language === "en" || !URL.canParse(target)) return target; + const url = new URL(target); + const prefix = `/${language}/`; + if ( + url.origin !== "https://rea.tools" || + !url.pathname.startsWith(prefix) + ) + return target; + url.pathname = url.pathname.slice(prefix.length - 1); + return url.href; + }) .sort(); const requireText = (issues, path, content, expected) => { @@ -83,10 +95,17 @@ export const documentationFactIssues = async (root, catalog) => { } } const canonicalLinks = readmeLinkTargets(readmes.get("README.md") ?? ""); + const languageInventory = JSON.parse( + await readFile(join(root, "website/locales/languages.json"), "utf8"), + ); + const readmeLanguages = new Map( + languageInventory.map(({ readme, code }) => [readme, code]), + ); for (const path of readmePaths.filter((path) => path !== "README.md")) if ( - JSON.stringify(readmeLinkTargets(readmes.get(path) ?? "")) !== - JSON.stringify(canonicalLinks) + JSON.stringify( + readmeLinkTargets(readmes.get(path) ?? "", readmeLanguages.get(path)), + ) !== JSON.stringify(canonicalLinks) ) issues.push(`${path}: documentation links differ from README.md`); diff --git a/tests/boundary/cli/productCatalog.test.ts b/tests/boundary/cli/productCatalog.test.ts index 3f35fc830..cbdabd45c 100644 --- a/tests/boundary/cli/productCatalog.test.ts +++ b/tests/boundary/cli/productCatalog.test.ts @@ -173,6 +173,7 @@ describe("canonical product catalog drift", () => { "README_ja.md", "README_ko.md", "README_ar.md", + "website/locales/languages.json", "docs/installation.md", "AGENTS.md", ".github/pull_request_template.md", @@ -187,22 +188,40 @@ describe("canonical product catalog drift", () => { }); const translatedPath = join(directory, "README_zh.md"); const translated = await readFile(translatedPath, "utf8"); - await writeFile( - translatedPath, - translated.replace( + const catalog = await createProductCatalog(root); + await expect(documentationFactIssues(directory, catalog)).resolves.toEqual( + [], + ); + for (const [original, changed] of [ + [ "(docs/installation.md#supported-agents)", "(docs/installation.md#missing-agents)", - ), - "utf8", - ); - - const issues = await documentationFactIssues( - directory, - await createProductCatalog(root), - ); - expect(issues).toContain( - "README_zh.md: documentation links differ from README.md", - ); + ], + [ + "https://rea.tools/zh-CN/guides/native/", + "https://rea.tools/zh-CN/missing/", + ], + [ + "https://rea.tools/zh-CN/guides/native/", + "https://rea.tools/zh-TW/guides/native/", + ], + [ + "https://rea.tools/zh-CN/guides/native/", + "https://rea.tools/zh-CN/guides/native/?other=1", + ], + ]) { + expect(translated).toContain(original); + await writeFile( + translatedPath, + translated.replace(original, changed), + "utf8", + ); + await expect( + documentationFactIssues(directory, catalog), + ).resolves.toEqual([ + "README_zh.md: documentation links differ from README.md", + ]); + } }); it("fails check mode without rewriting a stale generated artifact", async () => {