From 3a73826e28016e4451550555ca847d5be93dad5c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 27 Apr 2026 03:34:52 -0700 Subject: [PATCH] fix(docs-sync): prune orphan locale docs whose English source no longer exists The publish workflow rsyncs source docs/ into the publish repo with --delete, but explicitly protects locale directories so translation files survive non-translation-pipeline syncs. When an English source file is renamed (for example install/migrating-matrix.md -> channels/matrix-migration.md), the locale copies at /install/migrating-matrix.md become orphans: deleted from the English nav but still present on disk. Mintlify's hosted build appears to silently fall back to the previous deployment when nav references a path with mixed locale availability, so recent docs changes (the migration hub rework, matrix-migration move) are not propagating to docs.openclaw.ai even though every CI run reports success and the publish repo has the right English content. Add a pruneOrphanLocaleDocs() pass that walks every generated-locale directory in the publish target and removes any .md/.mdx file whose matching English path no longer exists in source docs. Runs after rsync and before composing docs.json so the regenerated nav and the on-disk files stay consistent. Verified the logic against the live publish repo: identifies all ja-JP/es/pt-BR/ko/de/fr/ar/it/tr/uk/id/pl/zh-CN orphans of install/migrating-matrix.md (12 entries) and would also catch any future renames the same way. --- scripts/docs-sync-publish.mjs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/scripts/docs-sync-publish.mjs b/scripts/docs-sync-publish.mjs index 0156a27c16d..326f13f0b7f 100644 --- a/scripts/docs-sync-publish.mjs +++ b/scripts/docs-sync-publish.mjs @@ -289,6 +289,32 @@ function composeDocsConfig() { }; } +function pruneOrphanLocaleDocs(targetDocsDir) { + let pruned = 0; + for (const locale of GENERATED_LOCALES) { + const localeDir = path.join(targetDocsDir, locale.dir); + if (!fs.existsSync(localeDir)) { + continue; + } + for (const filePath of walkMarkdownFiles(localeDir)) { + const relativeToLocale = path.relative(localeDir, filePath); + // The English source file lives at docs/ with either .md or .mdx. + const englishBase = path.join(SOURCE_DOCS_DIR, relativeToLocale); + const englishMd = englishBase.replace(/\.mdx?$/i, ".md"); + const englishMdx = englishBase.replace(/\.mdx?$/i, ".mdx"); + if (fs.existsSync(englishMd) || fs.existsSync(englishMdx)) { + continue; + } + fs.rmSync(filePath, { force: true }); + pruned += 1; + } + } + + if (pruned > 0) { + console.log(`Pruned ${pruned} orphan localized doc(s) with no matching English source file.`); + } +} + function repairGeneratedLocaleDocs(targetDocsDir) { let repaired = 0; for (const locale of GENERATED_LOCALES) { @@ -345,6 +371,7 @@ function syncDocsTree(targetRoot) { } } + pruneOrphanLocaleDocs(targetDocsDir); repairGeneratedLocaleDocs(targetDocsDir); writeJson(path.join(targetDocsDir, "docs.json"), composeDocsConfig()); }