diff --git a/.github/workflows/docs-validate-nav-build.yml b/.github/workflows/docs-validate-nav-build.yml index 674ca22a6ae..87a4e070c5e 100644 --- a/.github/workflows/docs-validate-nav-build.yml +++ b/.github/workflows/docs-validate-nav-build.yml @@ -40,6 +40,15 @@ jobs: run: | diff -q docs/nav.js docs/nav.js.original || (echo "Generated nav.js differs from committed version. Run 'npm run build' and commit the updated file." && exit 1) + # We do this after checking that nav.js matches nav.ts, to avoid confusing error messages if an author updates one but not the other. + # If the .ts and .js are in sync, it doesn't matter which one we check against here. + - name: Check that all files are listed in nav.js + working-directory: docs + run: | + cp docs/nav.js docs/nav.mjs + find docs -name '*.md' | sed 's!^docs/!!' > scripts/docs-files.txt + node scripts/checkNav.mjs docs-files.txt + - name: Restore original nav.js working-directory: docs if: success() || failure() diff --git a/docs/scripts/checkNav.mjs b/docs/scripts/checkNav.mjs new file mode 100644 index 00000000000..62f745ba690 --- /dev/null +++ b/docs/scripts/checkNav.mjs @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath, pathToFileURL } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const navPath = '../docs/nav.mjs'; + +const mdListPath = process.argv[2]; +if (!mdListPath) { + console.error('Usage: node checkNav.mjs '); + process.exit(1); +} + +const navFile = path.resolve(__dirname, navPath); +const nav = await import(pathToFileURL(navFile).href).then(mod => mod.default); + +const extractPathsFromNav = (items) => + items.filter(item => item.type === 'page').map(page => page.path); + +const navPaths = extractPathsFromNav(nav.items); +const navPathSet = new Set(navPaths); + +const expectedMdPaths = fs.readFileSync(path.resolve(__dirname, mdListPath), 'utf8') + .split('\n') + .map(line => line.trim()) + .filter(Boolean); +const expectedPathSet = new Set(expectedMdPaths); + +const missingInNav = expectedMdPaths.filter(p => !navPathSet.has(p)); +const extraInNav = navPaths.filter(p => !expectedPathSet.has(p)); + +let failed = false; + +if (missingInNav.length > 0) { + console.error('❌ These docs are missing from nav:'); + missingInNav.forEach(p => console.error(`- ${p}`)); + failed = true; +} + +if (extraInNav.length > 0) { + console.error('❌ These docs are listed in nav but not found under docs/:'); + extraInNav.forEach(p => console.error(`- ${p}`)); + failed = true; +} + +if (!failed) { + console.log('✅ nav list matches filesystem.'); +} else { + process.exit(1); +}