Skip to content

Commit 627d90d

Browse files
committed
feat(docs): add automatic sidebar sync for new documentation
1 parent 1be0793 commit 627d90d

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ docs-setup: ## One-time docs site setup (symlinks + npm install)
358358
@echo -e "$(GREEN)✅ Docs site setup complete!$(NC)"
359359

360360
docs-build: docs-setup ## Build documentation site (no serve)
361+
@echo "Syncing sidebar with new docs..."
362+
cd docs-site && node sync-sidebar.mjs
361363
@echo "Building documentation site..."
362364
cd docs-site && npm run build
363365
@echo -e "$(GREEN)✅ Docs site built! $(NC)"

docs-site/sync-sidebar.mjs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env node
2+
/**
3+
* sync-sidebar.mjs
4+
*
5+
* Detects docs/*.md files that are NOT listed in the manually‑curated sidebar
6+
* of astro.config.mjs and auto‑adds them to a "New & Uncategorized" section.
7+
*
8+
* Workflow:
9+
* 1. Scan docs/ for .md files → derive slugs (lowercase, no extension)
10+
* 2. Read astro.config.mjs, strip any existing auto‑generated section,
11+
* and extract all *manually* placed sidebar slugs
12+
* 3. Identify docs that have no manual sidebar entry
13+
* 4. If any are missing → inject (or update) a "New & Uncategorized" section
14+
* between marker comments so it can be re‑generated on the next run
15+
* 5. If none are missing → remove the auto section (user moved them all)
16+
*
17+
* Marker comments used in astro.config.mjs:
18+
* // AUTO-SIDEBAR-START
19+
* // AUTO-SIDEBAR-END
20+
*
21+
* Run: node docs-site/sync-sidebar.mjs (standalone)
22+
* make docs-build (integrated)
23+
*/
24+
25+
import { readdirSync, readFileSync, writeFileSync } from "fs";
26+
import { join, dirname } from "path";
27+
import { fileURLToPath } from "url";
28+
29+
const __dirname = dirname(fileURLToPath(import.meta.url));
30+
const projectRoot = join(__dirname, "..");
31+
const configPath = join(__dirname, "astro.config.mjs");
32+
33+
const MARKER_START = "// AUTO-SIDEBAR-START";
34+
const MARKER_END = "// AUTO-SIDEBAR-END";
35+
36+
// ---------------------------------------------------------------------------
37+
// Helpers
38+
// ---------------------------------------------------------------------------
39+
40+
function escapeRegex(s) {
41+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
42+
}
43+
44+
/** Convert a slug like "agent-analysis" → "Agent Analysis" */
45+
function slugToLabel(slug) {
46+
return slug
47+
.split("-")
48+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
49+
.join(" ");
50+
}
51+
52+
// ---------------------------------------------------------------------------
53+
// 1. Collect doc slugs from docs/
54+
// ---------------------------------------------------------------------------
55+
56+
const docFiles = readdirSync(join(projectRoot, "docs"))
57+
.filter((f) => f.endsWith(".md") && f !== "README.md")
58+
.map((f) => f.replace(/\.md$/, "").toLowerCase())
59+
.sort();
60+
61+
// ---------------------------------------------------------------------------
62+
// 2. Read config — strip auto section to get manual slugs only
63+
// ---------------------------------------------------------------------------
64+
65+
const config = readFileSync(configPath, "utf-8");
66+
67+
const autoSectionRegex = new RegExp(
68+
`[ \\t]*${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}[ \\t]*\\n?`,
69+
"m",
70+
);
71+
const manualConfig = config.replace(autoSectionRegex, "");
72+
73+
const manualSlugs = [...manualConfig.matchAll(/slug:\s*"([^"]+)"/g)].map(
74+
(m) => m[1],
75+
);
76+
77+
// ---------------------------------------------------------------------------
78+
// 3. Find missing docs
79+
// ---------------------------------------------------------------------------
80+
81+
const missing = docFiles.filter((slug) => !manualSlugs.includes(slug));
82+
83+
// ---------------------------------------------------------------------------
84+
// 4. Nothing missing — make sure auto section is removed and exit
85+
// ---------------------------------------------------------------------------
86+
87+
if (missing.length === 0) {
88+
if (config !== manualConfig) {
89+
// Auto section existed but is no longer needed — clean it up
90+
writeFileSync(configPath, manualConfig);
91+
console.log(
92+
"🧹 Removed empty auto‑generated sidebar section (all docs are manually placed).",
93+
);
94+
}
95+
console.log("✅ All docs are included in the sidebar.");
96+
process.exit(0);
97+
}
98+
99+
// ---------------------------------------------------------------------------
100+
// 5. Build the new auto section
101+
// ---------------------------------------------------------------------------
102+
103+
console.log(`\n📄 Found ${missing.length} new doc(s) not yet in sidebar:`);
104+
missing.forEach((slug) => console.log(` • docs/${slug}.md`));
105+
106+
const items = missing
107+
.map(
108+
(slug) =>
109+
` { label: "${slugToLabel(slug)}", slug: "${slug}" },`,
110+
)
111+
.join("\n");
112+
113+
const newSection = [
114+
` ${MARKER_START}`,
115+
` {`,
116+
` label: "New & Uncategorized",`,
117+
` items: [`,
118+
items,
119+
` ],`,
120+
` },`,
121+
` ${MARKER_END}`,
122+
].join("\n");
123+
124+
// ---------------------------------------------------------------------------
125+
// 6. Patch config
126+
// ---------------------------------------------------------------------------
127+
128+
let updatedConfig;
129+
130+
if (config.includes(MARKER_START)) {
131+
// Replace existing auto section in‑place
132+
updatedConfig = config.replace(autoSectionRegex, newSection + "\n");
133+
} else {
134+
// First time — insert just before the sidebar array's closing "],"
135+
// That bracket is at 6‑space indent and is the LAST such occurrence.
136+
const lines = config.split("\n");
137+
let insertAt = -1;
138+
for (let i = lines.length - 1; i >= 0; i--) {
139+
if (/^\s{6}\],/.test(lines[i])) {
140+
insertAt = i;
141+
break;
142+
}
143+
}
144+
if (insertAt === -1) {
145+
console.error(
146+
"❌ Could not locate the sidebar closing bracket in astro.config.mjs",
147+
);
148+
process.exit(1);
149+
}
150+
lines.splice(insertAt, 0, newSection);
151+
updatedConfig = lines.join("\n");
152+
}
153+
154+
writeFileSync(configPath, updatedConfig);
155+
156+
console.log(
157+
`\n✅ Auto‑added ${missing.length} doc(s) to "New & Uncategorized" sidebar section.`,
158+
);
159+
console.log(
160+
" 💡 Move them to the appropriate section in astro.config.mjs when ready.",
161+
);

0 commit comments

Comments
 (0)