Skip to content

Commit 3e12616

Browse files
committed
feat: use Docusaurus route metadata instead of filesystem scanning
Replaces findMarkdownFiles() filesystem scan with route.metadata.sourceFilePath from the postBuild routes prop. This automatically handles custom routeBasePath, versioned docs, and i18n without hardcoding paths. Resolves feedback from Docusaurus maintainer on PR #11623.
1 parent 2c913df commit 3e12616

2 files changed

Lines changed: 75 additions & 95 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ Thumbs.db
1818
.idea/
1919
*.swp
2020
*.swo
21+
22+
# Claude Code
23+
CLAUDE.md
24+
.claude/

index.js

Lines changed: 71 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,24 @@ function convertDetailsToMarkdown(content) {
5050
});
5151
}
5252

53+
// Flatten nested Docusaurus route tree into a flat array
54+
function flattenRoutes(routes) {
55+
return routes.flatMap(route => [
56+
route,
57+
...(route.routes ? flattenRoutes(route.routes) : []),
58+
]);
59+
}
60+
61+
// Strip baseUrl prefix from a URL path to get build-relative path
62+
function stripBaseUrl(urlPath, baseUrl) {
63+
if (baseUrl !== '/' && urlPath.startsWith(baseUrl)) {
64+
return urlPath.slice(baseUrl.length);
65+
}
66+
return urlPath.startsWith('/') ? urlPath.slice(1) : urlPath;
67+
}
68+
5369
// Clean markdown content for raw display - remove MDX/Docusaurus-specific syntax
54-
function cleanMarkdownForDisplay(content, filepath) {
55-
// Get the directory path for this file (relative to docs root)
56-
const fileDir = filepath.replace(/[^/]*$/, ''); // Remove filename, keep directory
70+
function cleanMarkdownForDisplay(content, routeDir) {
5771

5872
// 1. Strip YAML front matter (--- at start, content, then ---)
5973
content = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
@@ -98,13 +112,12 @@ function cleanMarkdownForDisplay(content, filepath) {
98112
// This runs AFTER Tabs/details conversion to preserve their content
99113
content = content.replace(/<[A-Z][a-zA-Z]*[\s\S]*?(?:\/>|<\/[A-Z][a-zA-Z]*>)/g, '');
100114

101-
// 10. Convert relative image paths to absolute paths from /docs/ root (Claude style)
115+
// 10. Convert relative image paths to absolute paths using route URL directory
102116
// Matches: ![alt](./img/file.png) or ![alt](img/file.png)
103117
content = content.replace(
104118
/!\[([^\]]*)\]\((\.\/)?img\/([^)]+)\)/g,
105119
(match, alt, relPrefix, filename) => {
106-
// Convert to absolute path: /docs/path/to/file/img/filename
107-
return `![${alt}](/docs/${fileDir}img/${filename})`;
120+
return `![${alt}](${routeDir}img/${filename})`;
108121
}
109122
);
110123

@@ -114,72 +127,6 @@ function cleanMarkdownForDisplay(content, filepath) {
114127
return content;
115128
}
116129

117-
// Recursively find all markdown files in a directory
118-
function findMarkdownFiles(dir, fileList = [], baseDir = dir) {
119-
const files = fs.readdirSync(dir);
120-
121-
files.forEach((file) => {
122-
const filePath = path.join(dir, file);
123-
const stat = fs.statSync(filePath);
124-
125-
if (stat.isDirectory()) {
126-
findMarkdownFiles(filePath, fileList, baseDir);
127-
} else if (file.endsWith('.md')) {
128-
// Store relative path from base directory
129-
const relativePath = path.relative(baseDir, filePath);
130-
fileList.push(relativePath);
131-
}
132-
});
133-
134-
return fileList;
135-
}
136-
137-
// Copy image directories from docs to build
138-
async function copyImageDirectories(docsDir, buildDir) {
139-
const imageDirs = [];
140-
141-
// Recursively find all 'img' directories in docs
142-
function findImgDirs(dir, baseDir = dir) {
143-
const files = fs.readdirSync(dir);
144-
145-
files.forEach((file) => {
146-
const filePath = path.join(dir, file);
147-
const stat = fs.statSync(filePath);
148-
149-
if (stat.isDirectory()) {
150-
if (file === 'img') {
151-
// Found an img directory, store its relative path
152-
const relativePath = path.relative(baseDir, dir);
153-
imageDirs.push({ source: filePath, relativePath });
154-
} else {
155-
// Continue searching in subdirectories
156-
findImgDirs(filePath, baseDir);
157-
}
158-
}
159-
});
160-
}
161-
162-
// Find all img directories
163-
findImgDirs(docsDir);
164-
165-
// Copy each img directory to build
166-
let copiedCount = 0;
167-
for (const { source, relativePath } of imageDirs) {
168-
const destination = path.join(buildDir, relativePath, 'img');
169-
170-
try {
171-
await fs.copy(source, destination);
172-
const imageCount = fs.readdirSync(source).length;
173-
console.log(` ✓ Copied: ${relativePath}/img/ (${imageCount} images)`);
174-
copiedCount++;
175-
} catch (error) {
176-
console.error(` ✗ Failed to copy ${relativePath}/img/:`, error.message);
177-
}
178-
}
179-
180-
return copiedCount;
181-
}
182-
183130
module.exports = function markdownSourcePlugin(context, options) {
184131
return {
185132
name: 'markdown-source-plugin',
@@ -189,47 +136,76 @@ module.exports = function markdownSourcePlugin(context, options) {
189136
return path.resolve(__dirname, './theme');
190137
},
191138

192-
async postBuild({ outDir }) {
193-
const docsDir = path.join(context.siteDir, 'docs');
194-
const buildDir = outDir;
139+
async postBuild({ outDir, routes, baseUrl }) {
140+
console.log('[markdown-source-plugin] Processing markdown source files...');
195141

196-
console.log('[markdown-source-plugin] Copying markdown source files...');
142+
// Flatten nested routes and filter to markdown sources
143+
const allRoutes = flattenRoutes(routes);
144+
const mdRoutes = allRoutes.filter(route => {
145+
const src = route.metadata?.sourceFilePath;
146+
return src && (src.endsWith('.md') || src.endsWith('.mdx'));
147+
});
197148

198-
// Find all markdown files in docs directory
199-
const mdFiles = findMarkdownFiles(docsDir);
149+
console.log(`[markdown-source-plugin] Found ${mdRoutes.length} markdown routes`);
200150

201151
let copiedCount = 0;
152+
const imgDirsToCopy = new Map(); // sourceImgDir -> destImgDir
202153

203-
// Process each markdown file to build directory
204-
for (const mdFile of mdFiles) {
205-
const sourcePath = path.join(docsDir, mdFile);
206-
const destPath = path.join(buildDir, mdFile);
154+
for (const route of mdRoutes) {
155+
const sourceRelPath = route.metadata.sourceFilePath;
156+
const sourcePath = path.join(context.siteDir, sourceRelPath);
207157

208-
try {
209-
// Ensure destination directory exists
210-
await fs.ensureDir(path.dirname(destPath));
158+
// Get route URL directory for image path rewriting
159+
const routeDir = route.path.endsWith('/')
160+
? route.path
161+
: route.path.replace(/[^/]+$/, '');
211162

212-
// Read the markdown file
213-
const content = await fs.readFile(sourcePath, 'utf8');
163+
// Construct the fetch URL the client dropdown will request
164+
const fetchUrl = route.path.endsWith('/')
165+
? route.path + 'intro.md'
166+
: route.path + '.md';
214167

215-
// Clean markdown for raw display
216-
const cleanedContent = cleanMarkdownForDisplay(content, mdFile);
168+
// Strip baseUrl to get build-relative path
169+
const buildRelPath = stripBaseUrl(fetchUrl, baseUrl);
170+
const destPath = path.join(outDir, buildRelPath);
217171

218-
// Write the cleaned content
172+
try {
173+
await fs.ensureDir(path.dirname(destPath));
174+
const content = await fs.readFile(sourcePath, 'utf8');
175+
const cleanedContent = cleanMarkdownForDisplay(content, routeDir);
219176
await fs.writeFile(destPath, cleanedContent, 'utf8');
220177
copiedCount++;
221-
222-
console.log(` ✓ Processed: ${mdFile}`);
178+
console.log(` ✓ Processed: ${sourceRelPath}${buildRelPath}`);
223179
} catch (error) {
224-
console.error(` ✗ Failed to process ${mdFile}:`, error.message);
180+
console.error(` ✗ Failed to process ${sourceRelPath}:`, error.message);
181+
}
182+
183+
// Track img directories near this source file for copying
184+
const sourceDir = path.dirname(sourcePath);
185+
const imgDir = path.join(sourceDir, 'img');
186+
if (!imgDirsToCopy.has(imgDir)) {
187+
const imgOutRelDir = stripBaseUrl(routeDir, baseUrl);
188+
imgDirsToCopy.set(imgDir, path.join(outDir, imgOutRelDir, 'img'));
225189
}
226190
}
227191

228-
console.log(`[markdown-source-plugin] Successfully copied ${copiedCount} markdown files`);
192+
console.log(`[markdown-source-plugin] Successfully processed ${copiedCount} markdown files`);
229193

230194
// Copy image directories
231195
console.log('[markdown-source-plugin] Copying image directories...');
232-
const imgDirCount = await copyImageDirectories(docsDir, buildDir);
196+
let imgDirCount = 0;
197+
for (const [source, dest] of imgDirsToCopy) {
198+
if (await fs.pathExists(source)) {
199+
try {
200+
await fs.copy(source, dest);
201+
const imageCount = fs.readdirSync(source).length;
202+
console.log(` ✓ Copied: ${path.relative(context.siteDir, source)} (${imageCount} files)`);
203+
imgDirCount++;
204+
} catch (error) {
205+
console.error(` ✗ Failed to copy ${path.relative(context.siteDir, source)}:`, error.message);
206+
}
207+
}
208+
}
233209
console.log(`[markdown-source-plugin] Successfully copied ${imgDirCount} image directories`);
234210
},
235211
};

0 commit comments

Comments
 (0)