Skip to content

Commit 985ca54

Browse files
chore: register Wave 7 pages in docs.json and add nav coverage check (Closes #122) (#135)
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.
1 parent 5ad06ef commit 985ca54

5 files changed

Lines changed: 263 additions & 12 deletions

File tree

.github/workflows/snippets.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
- name: Check snippets
3333
run: pnpm run check:snippets
3434

35+
- name: Check nav coverage
36+
run: pnpm run check:nav-coverage
37+
3538
stellar-testnet-snippets:
3639
name: Stellar snippet testnet validation
3740
runs-on: ubuntu-latest

docs.json

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"group": "Architecture",
8383
"pages": [
8484
"architecture/overview",
85+
"architecture/announcement-format",
8586
"architecture/chain-connectors",
8687
"architecture/tee",
8788
"architecture/stellar-cryptography",
@@ -90,11 +91,16 @@
9091
},
9192
{
9293
"group": "Contracts",
93-
"pages": ["contracts/evm", "contracts/stellar", "contracts/solana", "contracts/ckb", "reference/stellar-event-schemas"]
94+
"pages": ["contracts/evm", "contracts/stellar", "contracts/solana", "contracts/ckb"]
9495
},
9596
{
9697
"group": "Reference",
97-
"pages": ["reference/security-disclosure", "reference/threat-model", "reference/audits"]
98+
"pages": [
99+
"reference/audits",
100+
"reference/security-disclosure",
101+
"reference/stellar-networks",
102+
"reference/threat-model"
103+
]
98104
}
99105
]
100106
},
@@ -118,19 +124,27 @@
118124
"group": "Guides",
119125
"pages": [
120126
"guides/stealth-payments",
127+
"guides/stellar-quickstart",
128+
"guides/stellar-quickstart.es",
121129
"guides/single-chain-agent",
122130
"guides/multichain-agent",
123131
"guides/bring-your-own-model",
124132
"guides/privacy-best-practices",
125133
"guides/stellar-fees",
126134
"guides/stellar-troubleshooting",
127-
"guides/spectre-stellar-cookbook"
135+
"guides/spectre-stellar-cookbook",
136+
"guides/stellar-wallet-integration"
128137
]
129138
},
130139
{
131140
"group": "Integrations",
132141
"pages": [
133-
"guides/integrations/aquarius"
142+
"guides/integrations/aquarius",
143+
"guides/integrations/blend",
144+
"guides/integrations/nuxt",
145+
"guides/integrations/react-native",
146+
"guides/integrations/reflector",
147+
"guides/integrations/soroswap"
134148
]
135149
},
136150
{
@@ -146,13 +160,12 @@
146160
"guides/stellar-custom-assets",
147161
"guides/stellar/stellar-liquidity-pool-swap",
148162
"guides/stellar/stellar-path-payment",
163+
"guides/stellar/passkey-signing",
164+
"guides/stellar/stellar-quickstart",
165+
"guides/stellar/wraith-names-lifecycle",
149166
"guides/wraith-names-stellar",
150167
"guides/ops/self-hosted-deployment"
151168
]
152-
},
153-
{
154-
"group": "Integrations",
155-
"pages": ["guides/integrations/nuxt"]
156169
}
157170
]
158171
},
@@ -164,6 +177,7 @@
164177
"pages": [
165178
"api-reference/endpoints",
166179
"api-reference/fetch-announcements-stream",
180+
"api-reference/stealth-keys",
167181
"api-reference/types"
168182
]
169183
}

docs/CONTRIBUTING.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,30 @@ Use `no-check` only for intentionally illustrative pseudocode:
2727

2828
Prefer making snippets compile over opting them out.
2929

30+
## Navigation coverage
31+
32+
Every `.mdx` page in the shipped taxonomy (root pages plus `architecture/`,
33+
`api-reference/`, `contracts/`, `guides/`, `reference/`, and `sdk/`) must be
34+
registered in the `docs.json` navigation tree, and every navigation entry must
35+
resolve to a real file. This is enforced by:
36+
37+
```bash
38+
npm run check:nav-coverage
39+
```
40+
41+
The checker (`scripts/check-nav-coverage.mjs`) scans for `.mdx` files that are
42+
missing from `docs.json` and for nav entries that point at files that no longer
43+
exist. Run it after adding, renaming, or removing a page:
44+
45+
```bash
46+
node scripts/check-nav-coverage.mjs
47+
```
48+
49+
Note: `docs.json` is strict JSON — do not add `//` comments to it, the Mintlify
50+
CLI rejects them. Keep this file comment-free.
51+
3052
## CI
3153

32-
Every pull request runs the snippet checker through GitHub Actions. A separate
33-
non-blocking Stellar testnet job is reserved for end-to-end snippet validation
34-
that depends on network availability.
54+
Every pull request runs the snippet checker and the nav coverage check through
55+
GitHub Actions. A separate non-blocking Stellar testnet job is reserved for
56+
end-to-end snippet validation that depends on network availability.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
"type": "module",
55
"scripts": {
66
"check:snippets": "tsx scripts/check-snippets.ts",
7+
"check:nav-coverage": "node scripts/check-nav-coverage.mjs",
78
"check:stellar-testnet": "tsx scripts/check-stellar-testnet-snippets.ts",
89
"generate:stellar-reference": "tsx scripts/generate-stellar-reference.ts",
910
"check:stellar-reference": "tsx scripts/generate-stellar-reference.ts --check --allow-missing",
1011
"mint:validate": "mint validate",
1112
"mint:broken-links": "mint broken-links",
12-
"test": "npm run check:snippets"
13+
"test": "npm run check:snippets && npm run check:nav-coverage"
1314
},
1415
"dependencies": {
1516
"@solana/web3.js": "^1.95.0",

scripts/check-nav-coverage.mjs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env node
2+
/**
3+
* check-nav-coverage.mjs
4+
*
5+
* Verifies that every .mdx page in the shipped taxonomy is registered in the
6+
* docs.json navigation tree, and that every docs.json navigation entry
7+
* resolves to a real file. Run it after adding, renaming, or removing pages:
8+
*
9+
* node scripts/check-nav-coverage.mjs
10+
*
11+
* Wired into CI via the "Compile docs snippets" job in
12+
* .github/workflows/snippets.yml, so a PR that adds an .mdx page without
13+
* registering it in docs.json fails the build.
14+
*/
15+
import { readFile, readdir } from "node:fs/promises";
16+
import path from "node:path";
17+
import process from "node:process";
18+
19+
const repoRoot = process.cwd();
20+
const docsJsonPath = path.join(repoRoot, "docs.json");
21+
22+
/** Top-level directories whose pages ship in the navigation. */
23+
const shippedDirs = [
24+
"api-reference",
25+
"architecture",
26+
"concepts",
27+
"contracts",
28+
"guides",
29+
"reference",
30+
"sdk",
31+
];
32+
33+
async function main() {
34+
const docsJson = JSON.parse(stripComments(await readFile(docsJsonPath, "utf8")));
35+
const navEntries = collectNavEntries(docsJson.navigation);
36+
37+
const pagePaths = await collectPagePaths();
38+
const missingFromNav = [...pagePaths]
39+
.filter((page) => !navEntries.has(page))
40+
.sort();
41+
const missingOnDisk = [...navEntries]
42+
.filter((entry) => !isExternal(entry) && !pagePaths.has(entry))
43+
.sort();
44+
45+
const failures = [];
46+
if (missingFromNav.length > 0) {
47+
failures.push(
48+
[
49+
"Pages exist on disk but are missing from docs.json navigation:",
50+
...missingFromNav.map((page) => ` - ${page}`),
51+
].join("\n"),
52+
);
53+
}
54+
if (missingOnDisk.length > 0) {
55+
failures.push(
56+
[
57+
"docs.json navigation entries with no matching .mdx file on disk:",
58+
...missingOnDisk.map((entry) => ` - ${entry}`),
59+
].join("\n"),
60+
);
61+
}
62+
63+
if (failures.length > 0) {
64+
console.error(
65+
[
66+
"Nav coverage check failed.",
67+
"",
68+
...failures,
69+
"",
70+
"Register new pages in their natural group in docs.json, and remove nav",
71+
"entries that point at files that no longer exist.",
72+
].join("\n"),
73+
);
74+
process.exit(1);
75+
}
76+
77+
console.log(
78+
`Nav coverage passed: ${pagePaths.size} pages checked, ` +
79+
`${navEntries.size} nav entries verified.`,
80+
);
81+
}
82+
83+
/**
84+
* Collect every page path referenced anywhere in the navigation tree.
85+
* Handles nested groups, per-locale variants (e.g. guides/foo.es), and
86+
* object entries with a `page` field. External links are collected too and
87+
* filtered out by the on-disk check.
88+
*/
89+
function collectNavEntries(navigation) {
90+
const entries = new Set();
91+
const walk = (value, inPages) => {
92+
if (typeof value === "string") {
93+
if (inPages) entries.add(value);
94+
return;
95+
}
96+
if (Array.isArray(value)) {
97+
value.forEach((item) => walk(item, inPages));
98+
return;
99+
}
100+
if (value && typeof value === "object") {
101+
if (typeof value.page === "string") entries.add(value.page);
102+
for (const [key, child] of Object.entries(value)) {
103+
walk(child, inPages || key === "pages");
104+
}
105+
}
106+
};
107+
walk(navigation, false);
108+
return entries;
109+
}
110+
111+
/** Collect the nav path of every .mdx page in the shipped taxonomy. */
112+
async function collectPagePaths() {
113+
const pages = new Set();
114+
115+
const walk = async (dir) => {
116+
let entries;
117+
try {
118+
entries = await readdir(dir, { withFileTypes: true });
119+
} catch {
120+
return; // Directory does not exist (e.g. concepts/).
121+
}
122+
for (const entry of entries) {
123+
const fullPath = path.join(dir, entry.name);
124+
if (entry.isDirectory()) {
125+
await walk(fullPath);
126+
} else if (entry.isFile() && entry.name.endsWith(".mdx")) {
127+
pages.add(toNavPath(fullPath));
128+
}
129+
}
130+
};
131+
132+
for (const dir of shippedDirs) {
133+
await walk(path.join(repoRoot, dir));
134+
}
135+
136+
// Root-level pages live next to docs.json, outside the shipped dirs.
137+
const rootEntries = await readdir(repoRoot, { withFileTypes: true });
138+
for (const entry of rootEntries) {
139+
if (entry.isFile() && entry.name.endsWith(".mdx")) {
140+
pages.add(entry.name.replace(/\.mdx$/, ""));
141+
}
142+
}
143+
144+
return pages;
145+
}
146+
147+
/** Absolute path -> navigation path (relative, forward slashes, no .mdx). */
148+
function toNavPath(filePath) {
149+
return path.relative(repoRoot, filePath).split(path.sep).join("/").replace(/\.mdx$/, "");
150+
}
151+
152+
function isExternal(entry) {
153+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(entry) || entry.startsWith("//");
154+
}
155+
156+
/**
157+
* Strip line comments (//) and block comments (slash-star ... star-slash)
158+
* from JSONC so docs.json can carry the audit-script comment header.
159+
* Respects string literals so URLs like "https://..." are untouched.
160+
*/
161+
function stripComments(source) {
162+
let result = "";
163+
let inString = false;
164+
let i = 0;
165+
166+
while (i < source.length) {
167+
const char = source[i];
168+
const next = source[i + 1];
169+
170+
if (inString) {
171+
result += char;
172+
if (char === "\\" && next !== undefined) {
173+
result += next;
174+
i += 2;
175+
continue;
176+
}
177+
if (char === '"') inString = false;
178+
i += 1;
179+
continue;
180+
}
181+
182+
if (char === '"') {
183+
inString = true;
184+
result += char;
185+
i += 1;
186+
continue;
187+
}
188+
189+
if (char === "/" && next === "/") {
190+
while (i < source.length && source[i] !== "\n") i += 1;
191+
continue;
192+
}
193+
194+
if (char === "/" && next === "*") {
195+
i += 2;
196+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1;
197+
i += 2;
198+
continue;
199+
}
200+
201+
result += char;
202+
i += 1;
203+
}
204+
205+
return result;
206+
}
207+
208+
main().catch((error) => {
209+
console.error(error);
210+
process.exit(1);
211+
});

0 commit comments

Comments
 (0)