From 6f4e0d8ce2adb67ea33600411e37aab110a6726e Mon Sep 17 00:00:00 2001 From: franklin251861-killer Date: Mon, 24 Aug 2026 20:15:36 +0000 Subject: [PATCH] chore: register Wave 7 pages in docs.json and add nav coverage check (Closes #122) Wave 7 shipped many pages that were missing from the Mintlify navigation tree, leaving them reachable only via search or direct link. Register every on-disk page in its natural group, merge the duplicate Integrations groups, drop the dangling reference/stellar-event-schemas nav entry (the file no longer exists), and add the es locale variant next to its English source. Add scripts/check-nav-coverage.mjs, which fails when an .mdx page is missing from docs.json or a nav entry has no file, and wire it into the Compile docs snippets CI job so future waves cannot skip registration. Docs.json is strict JSON (the Mintlify CLI rejects comments), so the audit script is documented in docs/CONTRIBUTING.md instead of a comment header. --- .github/workflows/snippets.yml | 3 + docs.json | 30 +++-- docs/CONTRIBUTING.md | 28 ++++- package.json | 3 +- scripts/check-nav-coverage.mjs | 211 +++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 12 deletions(-) create mode 100644 scripts/check-nav-coverage.mjs diff --git a/.github/workflows/snippets.yml b/.github/workflows/snippets.yml index f33cde5..c6ee1ed 100644 --- a/.github/workflows/snippets.yml +++ b/.github/workflows/snippets.yml @@ -32,6 +32,9 @@ jobs: - name: Check snippets run: pnpm run check:snippets + - name: Check nav coverage + run: pnpm run check:nav-coverage + stellar-testnet-snippets: name: Stellar snippet testnet validation runs-on: ubuntu-latest diff --git a/docs.json b/docs.json index 268920e..4bd9a68 100644 --- a/docs.json +++ b/docs.json @@ -82,6 +82,7 @@ "group": "Architecture", "pages": [ "architecture/overview", + "architecture/announcement-format", "architecture/chain-connectors", "architecture/tee", "architecture/stellar-cryptography", @@ -90,11 +91,16 @@ }, { "group": "Contracts", - "pages": ["contracts/evm", "contracts/stellar", "contracts/solana", "contracts/ckb", "reference/stellar-event-schemas"] + "pages": ["contracts/evm", "contracts/stellar", "contracts/solana", "contracts/ckb"] }, { "group": "Reference", - "pages": ["reference/security-disclosure", "reference/threat-model", "reference/audits"] + "pages": [ + "reference/audits", + "reference/security-disclosure", + "reference/stellar-networks", + "reference/threat-model" + ] } ] }, @@ -118,19 +124,27 @@ "group": "Guides", "pages": [ "guides/stealth-payments", + "guides/stellar-quickstart", + "guides/stellar-quickstart.es", "guides/single-chain-agent", "guides/multichain-agent", "guides/bring-your-own-model", "guides/privacy-best-practices", "guides/stellar-fees", "guides/stellar-troubleshooting", - "guides/spectre-stellar-cookbook" + "guides/spectre-stellar-cookbook", + "guides/stellar-wallet-integration" ] }, { "group": "Integrations", "pages": [ - "guides/integrations/aquarius" + "guides/integrations/aquarius", + "guides/integrations/blend", + "guides/integrations/nuxt", + "guides/integrations/react-native", + "guides/integrations/reflector", + "guides/integrations/soroswap" ] }, { @@ -146,13 +160,12 @@ "guides/stellar-custom-assets", "guides/stellar/stellar-liquidity-pool-swap", "guides/stellar/stellar-path-payment", + "guides/stellar/passkey-signing", + "guides/stellar/stellar-quickstart", + "guides/stellar/wraith-names-lifecycle", "guides/wraith-names-stellar", "guides/ops/self-hosted-deployment" ] - }, - { - "group": "Integrations", - "pages": ["guides/integrations/nuxt"] } ] }, @@ -164,6 +177,7 @@ "pages": [ "api-reference/endpoints", "api-reference/fetch-announcements-stream", + "api-reference/stealth-keys", "api-reference/types" ] } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 597665e..67593e2 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -27,8 +27,30 @@ Use `no-check` only for intentionally illustrative pseudocode: Prefer making snippets compile over opting them out. +## Navigation coverage + +Every `.mdx` page in the shipped taxonomy (root pages plus `architecture/`, +`api-reference/`, `contracts/`, `guides/`, `reference/`, and `sdk/`) must be +registered in the `docs.json` navigation tree, and every navigation entry must +resolve to a real file. This is enforced by: + +```bash +npm run check:nav-coverage +``` + +The checker (`scripts/check-nav-coverage.mjs`) scans for `.mdx` files that are +missing from `docs.json` and for nav entries that point at files that no longer +exist. Run it after adding, renaming, or removing a page: + +```bash +node scripts/check-nav-coverage.mjs +``` + +Note: `docs.json` is strict JSON — do not add `//` comments to it, the Mintlify +CLI rejects them. Keep this file comment-free. + ## CI -Every pull request runs the snippet checker through GitHub Actions. A separate -non-blocking Stellar testnet job is reserved for end-to-end snippet validation -that depends on network availability. +Every pull request runs the snippet checker and the nav coverage check through +GitHub Actions. A separate non-blocking Stellar testnet job is reserved for +end-to-end snippet validation that depends on network availability. diff --git a/package.json b/package.json index e364273..1acf7bb 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,13 @@ "type": "module", "scripts": { "check:snippets": "tsx scripts/check-snippets.ts", + "check:nav-coverage": "node scripts/check-nav-coverage.mjs", "check:stellar-testnet": "tsx scripts/check-stellar-testnet-snippets.ts", "generate:stellar-reference": "tsx scripts/generate-stellar-reference.ts", "check:stellar-reference": "tsx scripts/generate-stellar-reference.ts --check --allow-missing", "mint:validate": "mint validate", "mint:broken-links": "mint broken-links", - "test": "npm run check:snippets" + "test": "npm run check:snippets && npm run check:nav-coverage" }, "dependencies": { "@solana/web3.js": "^1.95.0", diff --git a/scripts/check-nav-coverage.mjs b/scripts/check-nav-coverage.mjs new file mode 100644 index 0000000..4cb26eb --- /dev/null +++ b/scripts/check-nav-coverage.mjs @@ -0,0 +1,211 @@ +#!/usr/bin/env node +/** + * check-nav-coverage.mjs + * + * Verifies that every .mdx page in the shipped taxonomy is registered in the + * docs.json navigation tree, and that every docs.json navigation entry + * resolves to a real file. Run it after adding, renaming, or removing pages: + * + * node scripts/check-nav-coverage.mjs + * + * Wired into CI via the "Compile docs snippets" job in + * .github/workflows/snippets.yml, so a PR that adds an .mdx page without + * registering it in docs.json fails the build. + */ +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import process from "node:process"; + +const repoRoot = process.cwd(); +const docsJsonPath = path.join(repoRoot, "docs.json"); + +/** Top-level directories whose pages ship in the navigation. */ +const shippedDirs = [ + "api-reference", + "architecture", + "concepts", + "contracts", + "guides", + "reference", + "sdk", +]; + +async function main() { + const docsJson = JSON.parse(stripComments(await readFile(docsJsonPath, "utf8"))); + const navEntries = collectNavEntries(docsJson.navigation); + + const pagePaths = await collectPagePaths(); + const missingFromNav = [...pagePaths] + .filter((page) => !navEntries.has(page)) + .sort(); + const missingOnDisk = [...navEntries] + .filter((entry) => !isExternal(entry) && !pagePaths.has(entry)) + .sort(); + + const failures = []; + if (missingFromNav.length > 0) { + failures.push( + [ + "Pages exist on disk but are missing from docs.json navigation:", + ...missingFromNav.map((page) => ` - ${page}`), + ].join("\n"), + ); + } + if (missingOnDisk.length > 0) { + failures.push( + [ + "docs.json navigation entries with no matching .mdx file on disk:", + ...missingOnDisk.map((entry) => ` - ${entry}`), + ].join("\n"), + ); + } + + if (failures.length > 0) { + console.error( + [ + "Nav coverage check failed.", + "", + ...failures, + "", + "Register new pages in their natural group in docs.json, and remove nav", + "entries that point at files that no longer exist.", + ].join("\n"), + ); + process.exit(1); + } + + console.log( + `Nav coverage passed: ${pagePaths.size} pages checked, ` + + `${navEntries.size} nav entries verified.`, + ); +} + +/** + * Collect every page path referenced anywhere in the navigation tree. + * Handles nested groups, per-locale variants (e.g. guides/foo.es), and + * object entries with a `page` field. External links are collected too and + * filtered out by the on-disk check. + */ +function collectNavEntries(navigation) { + const entries = new Set(); + const walk = (value, inPages) => { + if (typeof value === "string") { + if (inPages) entries.add(value); + return; + } + if (Array.isArray(value)) { + value.forEach((item) => walk(item, inPages)); + return; + } + if (value && typeof value === "object") { + if (typeof value.page === "string") entries.add(value.page); + for (const [key, child] of Object.entries(value)) { + walk(child, inPages || key === "pages"); + } + } + }; + walk(navigation, false); + return entries; +} + +/** Collect the nav path of every .mdx page in the shipped taxonomy. */ +async function collectPagePaths() { + const pages = new Set(); + + const walk = async (dir) => { + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + return; // Directory does not exist (e.g. concepts/). + } + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + await walk(fullPath); + } else if (entry.isFile() && entry.name.endsWith(".mdx")) { + pages.add(toNavPath(fullPath)); + } + } + }; + + for (const dir of shippedDirs) { + await walk(path.join(repoRoot, dir)); + } + + // Root-level pages live next to docs.json, outside the shipped dirs. + const rootEntries = await readdir(repoRoot, { withFileTypes: true }); + for (const entry of rootEntries) { + if (entry.isFile() && entry.name.endsWith(".mdx")) { + pages.add(entry.name.replace(/\.mdx$/, "")); + } + } + + return pages; +} + +/** Absolute path -> navigation path (relative, forward slashes, no .mdx). */ +function toNavPath(filePath) { + return path.relative(repoRoot, filePath).split(path.sep).join("/").replace(/\.mdx$/, ""); +} + +function isExternal(entry) { + return /^[a-z][a-z0-9+.-]*:\/\//i.test(entry) || entry.startsWith("//"); +} + +/** + * Strip line comments (//) and block comments (slash-star ... star-slash) + * from JSONC so docs.json can carry the audit-script comment header. + * Respects string literals so URLs like "https://..." are untouched. + */ +function stripComments(source) { + let result = ""; + let inString = false; + let i = 0; + + while (i < source.length) { + const char = source[i]; + const next = source[i + 1]; + + if (inString) { + result += char; + if (char === "\\" && next !== undefined) { + result += next; + i += 2; + continue; + } + if (char === '"') inString = false; + i += 1; + continue; + } + + if (char === '"') { + inString = true; + result += char; + i += 1; + continue; + } + + if (char === "/" && next === "/") { + while (i < source.length && source[i] !== "\n") i += 1; + continue; + } + + if (char === "/" && next === "*") { + i += 2; + while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1; + i += 2; + continue; + } + + result += char; + i += 1; + } + + return result; +} + +main().catch((error) => { + console.error(error); + process.exit(1); +});