diff --git a/plugins/docusaurus-plugin-markdown-export/index.js b/plugins/docusaurus-plugin-markdown-export/index.js index b05e856b..83c9afe6 100644 --- a/plugins/docusaurus-plugin-markdown-export/index.js +++ b/plugins/docusaurus-plugin-markdown-export/index.js @@ -3,7 +3,9 @@ const path = require('path'); const { processMarkdownFile, loadConstants } = require('./mdxProcessor'); module.exports = function pluginMarkdownExport(context, options = {}) { - const { siteDir } = context; + const { siteDir, siteConfig } = context; + const siteUrl = (siteConfig?.url || '').replace(/\/$/, ''); + const baseUrl = siteConfig?.baseUrl || '/'; // Auto-detect versions from versions.json (no manual updates needed) function getVersionConfig() { @@ -127,13 +129,15 @@ module.exports = function pluginMarkdownExport(context, options = {}) { const slug = relativePath.replace(/\.(md|mdx)$/, '').split(path.sep).join('/'); const versionPrefix = getVersionPrefix(versionName); const outputPath = path.join(outputBaseDir, 'docs', versionPrefix, slug + '.md'); + const docUrlPath = (baseUrl + 'docs/' + versionPrefix + slug).replace(/\/+/g, '/'); try { const sourceContent = fs.readFileSync(filePath, 'utf-8'); const cleanMarkdown = await processMarkdownFile( sourceContent, constants, - path.dirname(filePath) + path.dirname(filePath), + { docUrlPath, siteUrl } ); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); @@ -180,13 +184,15 @@ module.exports = function pluginMarkdownExport(context, options = {}) { for (const { fullPath, slug } of files) { const outputPath = path.join(outputBaseDir, 'docs', versionPrefix, slug + '.md'); + const docUrlPath = (baseUrl + 'docs/' + versionPrefix + slug).replace(/\/+/g, '/'); try { const sourceContent = fs.readFileSync(fullPath, 'utf-8'); const cleanMarkdown = await processMarkdownFile( sourceContent, constants, - path.dirname(fullPath) + path.dirname(fullPath), + { docUrlPath, siteUrl } ); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); diff --git a/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js b/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js index b79abc81..09b3b02f 100644 --- a/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js +++ b/plugins/docusaurus-plugin-markdown-export/mdxProcessor.js @@ -32,9 +32,17 @@ async function loadConstants(constantsPath) { } /** - * Process MDX content and convert to clean markdown + * Process MDX content and convert to clean markdown. + * + * `linkContext`, when provided, is used to resolve relative doc links into + * absolute URLs so the exported `.md` works when read in isolation by an + * LLM or any consumer that doesn't have a base URL to resolve against. + * linkContext: { docUrlPath, siteUrl } + * docUrlPath: site-absolute URL of the current doc, no extension, + * e.g. "/docs/v1.0.x/getting-started/try-it-out/on-k3d-locally" + * siteUrl: e.g. "https://openchoreo.dev" */ -async function processMarkdownFile(content, constants, sourceDir) { +async function processMarkdownFile(content, constants, sourceDir, linkContext) { let result = content; // Step 1: Remove import statements @@ -65,7 +73,11 @@ async function processMarkdownFile(content, constants, sourceDir) { // Step 9: Process Link components result = processLinks(result, constants); - // Step 10: Clean up remaining JSX/HTML artifacts + // Step 10: Rewrite relative doc links to absolute `.md` URLs so they + // resolve when the file is fetched standalone. + result = rewriteRelativeDocLinks(result, linkContext); + + // Step 11: Clean up remaining JSX/HTML artifacts result = cleanupArtifacts(result); // Step 11: Clean up excessive whitespace @@ -177,11 +189,13 @@ function processContentSections(content) { function replaceVersionInterpolations(content, constants) { let result = content; - // Replace {versions.xxx} patterns (JSX interpolation) - result = result.replace(/\{versions\.dockerTag\}/g, constants.dockerTag || 'latest'); - result = result.replace(/\{versions\.githubRef\}/g, constants.githubRef || 'main'); - result = result.replace(/\{versions\.helmChart\}/g, constants.helmChart || ''); - result = result.replace(/\{versions\.helmSource\}/g, constants.helmSource || ''); + // Match both `{versions.x}` (JSX interpolation in body text) and + // `${versions.x}` (template-literal interpolation inside JSX attributes). + // Without `\$?`, the `$` from `${...}` is left orphaned in the output. + result = result.replace(/\$?\{versions\.dockerTag\}/g, constants.dockerTag || 'latest'); + result = result.replace(/\$?\{versions\.githubRef\}/g, constants.githubRef || 'main'); + result = result.replace(/\$?\{versions\.helmChart\}/g, constants.helmChart || ''); + result = result.replace(/\$?\{versions\.helmSource\}/g, constants.helmSource || ''); return result; } @@ -233,6 +247,93 @@ function processLinks(content, constants) { return result; } +// Rewrite relative doc links so they resolve when the .md is fetched +// standalone by an LLM or any consumer that has no base URL to resolve +// against. Handles: +// - `./foo`, `../foo`, `foo/bar` (relative paths) → absolute `https://…/