Skip to content

Commit b2e3556

Browse files
authored
fix(docs): recurse into docs subdirectories when preparing for Docusaurus (#32)
The Docusaurus prepare step at website/scripts/prepare-docs.js used a non-recursive readdirSync over docs/, so it silently skipped any subdirectory. The recently merged docs/adr/ section therefore never reached the published site, despite the build appearing to succeed. Recurse the source tree, preserving directory structure on the way to website/docs/. For nested files we omit explicit `id` frontmatter so that Docusaurus derives both the id and the route from the file's path, which is the cleanest way to give nested docs unique URLs without hitting Docusaurus's "id cannot include slash" rule. Top-level docs keep their explicit id and existing URL, so this is a backward- compatible change. Verified locally with `npm run build` in website/: docs/adr/README.md now renders at /docs/adr and docs/adr/0001-aws-identity-center.md at /docs/adr/aws-identity-center.
1 parent 6538d6f commit b2e3556

1 file changed

Lines changed: 60 additions & 30 deletions

File tree

website/scripts/prepare-docs.js

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22
/**
33
* Prepares docs for Docusaurus by:
4-
* 1. Copying from ../docs/ to ./docs/
4+
* 1. Copying from ../docs/ to ./docs/ (recursively, preserving subdirectories)
55
* 2. Adding frontmatter (id, title, sidebar_position)
66
* 3. Escaping MDX special characters
77
*/
@@ -38,13 +38,17 @@ function extractPosition(filename) {
3838
return 999;
3939
}
4040

41-
// Generate document ID from filename
42-
function extractId(filename) {
43-
if (filename === 'README.md') {
44-
return 'index';
45-
}
46-
// Keep the number prefix to ensure unique IDs (e.g., "00-index" not "index")
47-
return filename.replace(/\.md$/, '');
41+
// Generate document ID from filename and relative directory.
42+
// IDs must be unique across the docs tree but cannot contain slashes
43+
// (Docusaurus rejects them in frontmatter ids), so we join the relative
44+
// directory and leaf with hyphens. The route URL is derived from the
45+
// file's path on disk and is unaffected by this id.
46+
function extractId(filename, relativeDir) {
47+
const base = filename === 'README.md'
48+
? 'index'
49+
: filename.replace(/\.md$/, '');
50+
if (!relativeDir) return base;
51+
return `${relativeDir.replace(/[\\/]+/g, '-')}-${base}`;
4852
}
4953

5054
// Escape MDX special characters in content (but not in code blocks)
@@ -89,34 +93,50 @@ function hasFrontmatter(content) {
8993
return content.startsWith('---\n');
9094
}
9195

92-
// Process a single markdown file
93-
function processFile(filename) {
94-
const sourcePath = path.join(SOURCE_DIR, filename);
95-
const targetPath = path.join(TARGET_DIR, filename);
96+
// Process a single markdown file. `relativeDir` is the path of the file's
97+
// containing directory relative to SOURCE_DIR (empty string for top-level).
98+
function processFile(filename, relativeDir) {
99+
const sourcePath = path.join(SOURCE_DIR, relativeDir, filename);
100+
const targetDir = path.join(TARGET_DIR, relativeDir);
101+
const targetPath = path.join(targetDir, filename);
102+
103+
if (!fs.existsSync(targetDir)) {
104+
fs.mkdirSync(targetDir, { recursive: true });
105+
}
96106

97107
let content = fs.readFileSync(sourcePath, 'utf8');
98108

99109
// Skip if already has frontmatter (shouldn't happen with clean source)
100110
if (hasFrontmatter(content)) {
101-
console.log(` Skipping ${filename} (already has frontmatter)`);
111+
console.log(` Skipping ${path.join(relativeDir, filename)} (already has frontmatter)`);
102112
fs.writeFileSync(targetPath, content);
103113
return;
104114
}
105115

106-
const id = extractId(filename);
107116
const title = extractTitle(content, filename);
108117
const position = extractPosition(filename);
109118

110-
// Build frontmatter
111-
const frontmatter = [
112-
'---',
113-
`id: ${id}`,
119+
// Build frontmatter. We only set an explicit `id` for top-level docs,
120+
// where it preserves backward-compatible URLs and matches the existing
121+
// sidebar ordering. For nested docs we omit `id` entirely so that
122+
// Docusaurus derives both the id and the route from the file path,
123+
// giving stable URLs like `/docs/adr/0001-aws-identity-center` without
124+
// any need to encode the directory in the id (which Docusaurus rejects
125+
// when it contains slashes).
126+
const frontmatter = ['---'];
127+
if (relativeDir === '') {
128+
const id = extractId(filename, relativeDir);
129+
frontmatter.push(`id: ${id}`);
130+
}
131+
frontmatter.push(
114132
`title: "${title.replace(/"/g, '\\"')}"`,
115133
`sidebar_position: ${position}`,
116-
];
134+
);
117135

118-
// Add slug for README to make it the index
119-
if (filename === 'README.md') {
136+
// Add slug for the top-level README to make it the docs index. Nested
137+
// README/index files become the index of their containing folder
138+
// automatically.
139+
if (filename === 'README.md' && relativeDir === '') {
120140
frontmatter.push('slug: /');
121141
}
122142

@@ -129,7 +149,24 @@ function processFile(filename) {
129149
const output = frontmatter.join('\n') + content;
130150

131151
fs.writeFileSync(targetPath, output);
132-
console.log(` Processed ${filename} -> id: ${id}, position: ${position}`);
152+
console.log(` Processed ${path.join(relativeDir, filename)} -> position: ${position}`);
153+
}
154+
155+
// Walk the source tree, processing every markdown file we find. Skips
156+
// dotfiles and dot-directories so that `docs/.meta/` and similar provenance
157+
// folders are left alone.
158+
function walk(relativeDir) {
159+
const dir = path.join(SOURCE_DIR, relativeDir);
160+
const entries = fs.readdirSync(dir, { withFileTypes: true })
161+
.filter(e => !e.name.startsWith('.'));
162+
163+
for (const entry of entries) {
164+
if (entry.isDirectory()) {
165+
walk(path.join(relativeDir, entry.name));
166+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
167+
processFile(entry.name, relativeDir);
168+
}
169+
}
133170
}
134171

135172
// Main
@@ -144,14 +181,7 @@ function main() {
144181
}
145182
fs.mkdirSync(TARGET_DIR, { recursive: true });
146183

147-
// Get all markdown files
148-
const files = fs.readdirSync(SOURCE_DIR)
149-
.filter(f => f.endsWith('.md') && !f.startsWith('.'));
150-
151-
console.log(` Found ${files.length} markdown files`);
152-
153-
// Process each file
154-
files.forEach(processFile);
184+
walk('');
155185

156186
console.log('Done!');
157187
}

0 commit comments

Comments
 (0)