Skip to content

Commit 2e8b482

Browse files
authored
feat: add markdown output plugin to copy markdown files alongside HTML (#882)
* feat: add markdown output plugin to copy markdown files alongside HTML * Remove async, simplify
1 parent 5ec853e commit 2e8b482

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

docusaurus.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ const config: Config = {
242242
path.resolve(__dirname, './src/plugins/releases/index.ts'),
243243
path.resolve(__dirname, './src/plugins/fiddle/index.ts'),
244244
path.resolve(__dirname, './src/plugins/governance/index.ts'),
245+
path.resolve(__dirname, './src/plugins/markdown-output/index.ts'),
245246
],
246247
presets: [
247248
[
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Plugin to output index.md files alongside index.html in the build directory.
3+
* This copies the original markdown source files to their corresponding locations
4+
* in the build output.
5+
*/
6+
7+
import fs from 'node:fs';
8+
import path from 'node:path';
9+
import { Plugin } from '@docusaurus/types';
10+
import logger from '@docusaurus/logger';
11+
12+
/** Route prefixes to copy markdown files within. */
13+
const PREFIXES = ['docs/', 'blog/'];
14+
15+
type Context = Parameters<Plugin['postBuild']>[0];
16+
17+
function findMarkdownSource(
18+
routePath: string,
19+
{ siteDir }: Context,
20+
): string | null {
21+
for (const prefix of PREFIXES) {
22+
const routePrefix = `/${prefix}`;
23+
24+
if (!routePath.startsWith(routePrefix)) continue;
25+
26+
// Since routePrefix is absolute and routePath starts with routePrefix, it too must be absolute
27+
// meaning we can safely use path.relative to get the relative path
28+
const relativePath = path.relative(routePrefix, routePath);
29+
const sourceDir = path.join(siteDir, prefix);
30+
31+
// Try direct file path first (e.g., /docs/tutorial/intro.md)
32+
const directPath = path.join(sourceDir, `${relativePath}.md`);
33+
if (fs.existsSync(directPath)) return directPath;
34+
35+
// Try index.md in directory (e.g., /docs/tutorial/intro/index.md)
36+
const indexPath = path.join(sourceDir, relativePath, 'index.md');
37+
if (fs.existsSync(indexPath)) return indexPath;
38+
}
39+
40+
return null;
41+
}
42+
43+
function copyMarkdownForRoute(routePath: string, context: Context): boolean {
44+
const { outDir } = context;
45+
46+
// Find corresponding markdown source
47+
const mdSourcePath = findMarkdownSource(routePath, context);
48+
if (!mdSourcePath) return false;
49+
50+
// Check if HTML output exists
51+
const htmlPath = path.join(outDir, routePath, 'index.html');
52+
if (!fs.existsSync(htmlPath)) return false;
53+
54+
// Copy markdown to output directory
55+
const outputMdPath = path.join(outDir, routePath, 'index.md');
56+
fs.copyFileSync(mdSourcePath, outputMdPath);
57+
58+
return true;
59+
}
60+
61+
export default function markdownOutputPlugin(): Plugin {
62+
return {
63+
name: 'markdown-output-plugin',
64+
postBuild(context) {
65+
logger.info('Copying markdown files to build directory...');
66+
67+
// Filter to only routes in target prefixes
68+
const routes = context.routesPaths.filter((routePath) =>
69+
PREFIXES.some((prefix) => routePath.startsWith(`/${prefix}`)),
70+
);
71+
72+
// Process all routes
73+
let copiedCount = 0;
74+
for (const routePath of routes) {
75+
if (copyMarkdownForRoute(routePath, context)) {
76+
copiedCount += 1;
77+
}
78+
}
79+
80+
logger.success(`Copied ${copiedCount} markdown files to build directory`);
81+
},
82+
};
83+
}

0 commit comments

Comments
 (0)