Skip to main content
Glama
mdx-converter.ts5.45 kB
import { writeFile, mkdir } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { existsSync } from 'node:fs'; import frontMatter from 'front-matter'; import type { MdxFile, ConversionResult, CompiledFile } from './types.js'; export class MdxConverter { private verbose: boolean; constructor(verbose = false) { this.verbose = verbose; } /** * Fix local links in markdown content */ private fixLocalLinks(content: string): string { // Convert .mdx extensions to .md content = content.replace(/\]\(([^)]*)?\.mdx([^)]*?)\)/g, ']($1.md$2)'); // Convert directory-only links to file links (e.g., ./audio/ -> ./audio.md) content = content.replace(/\]\(\.\/([^/#)]+)\/\)/g, '](./$1.md)'); // Handle directory links with fragments (e.g., ./audio/#section -> ./audio.md#section) content = content.replace(/\]\(\.\/([^/#)]+)\/([^)]*)\)/g, '](./$1.md$2)'); return content; } /** * Convert MDX content to Markdown */ private convertMdxToMarkdown(content: string): string { let markdown = content; // Remove import statements markdown = markdown.replace(/^import\s+.*?from\s+['"][^'"]*['"];?\s*$/gm, ''); markdown = markdown.replace(/^import\s+['"][^'"]*['"];?\s*$/gm, ''); // Remove JSX components but preserve their content // Handle self-closing components: <Component /> markdown = markdown.replace(/<([A-Z][a-zA-Z0-9]*)[^>]*\/>/g, ''); // Handle component with content: <Component>content</Component> markdown = markdown.replace(/<([A-Z][a-zA-Z0-9]*)[^>]*>([\s\S]*?)<\/\1>/g, '$2'); // Remove export statements markdown = markdown.replace(/^export\s+.*?;?\s*$/gm, ''); // Fix local links markdown = this.fixLocalLinks(markdown); // Clean up extra newlines markdown = markdown.replace(/\n{3,}/g, '\n\n'); return markdown.trim(); } /** * Process a single MDX file */ private async processMdxFile(mdxFile: MdxFile, outputDir: string): Promise<ConversionResult> { try { // Extract frontmatter const { attributes, body } = frontMatter(mdxFile.content); // Convert MDX to Markdown const markdown = this.convertMdxToMarkdown(body); // Reconstruct the document with frontmatter let output = ''; const attrs = attributes as Record<string, any>; if (Object.keys(attrs).length > 0) { output += '---\n'; for (const [key, value] of Object.entries(attrs)) { if (typeof value === 'string') { output += `${key}: ${value}\n`; } else if (Array.isArray(value)) { output += `${key}: [${value.map(v => `"${v}"`).join(', ')}]\n`; } else { output += `${key}: ${JSON.stringify(value)}\n`; } } output += '---\n\n'; } output += markdown; // Determine output path const outputPath = join(outputDir, mdxFile.relativePath.replace(/\.mdx$/, '.md')); // Ensure output directory exists const outputDirPath = dirname(outputPath); if (!existsSync(outputDirPath)) { await mkdir(outputDirPath, { recursive: true }); } // Write the converted file await writeFile(outputPath, output, 'utf-8'); if (this.verbose) { console.log(`✅ Converted: ${mdxFile.relativePath}`); } // Create compiled file object const compiledFile: CompiledFile = { path: outputPath, content: output, relativePath: mdxFile.relativePath.replace(/\.mdx$/, '.md') }; return { success: true, path: outputPath, compiledFile }; } catch (error) { return { success: false, path: mdxFile.relativePath, error: error instanceof Error ? error.message : String(error) }; } } /** * Convert all MDX files to Markdown */ async convertFiles(mdxFiles: Map<string, MdxFile>, outputDir: string): Promise<Map<string, CompiledFile>> { console.log(`🔄 Converting ${mdxFiles.size} MDX files to Markdown...`); let successCount = 0; let errorCount = 0; const errors: ConversionResult[] = []; const compiledFiles = new Map<string, CompiledFile>(); // Process files sequentially to avoid overwhelming the filesystem for (const [relativePath, mdxFile] of mdxFiles) { const result = await this.processMdxFile(mdxFile, outputDir); if (result.success && result.compiledFile) { successCount++; compiledFiles.set(relativePath, result.compiledFile); } else { errorCount++; errors.push(result); console.error(`❌ Failed to convert ${relativePath}: ${result.error}`); } } // Summary console.log(`\n📊 Conversion Summary:`); console.log(`✅ Successfully converted: ${successCount} files`); console.log(`❌ Failed conversions: ${errorCount} files`); if (errors.length > 0) { console.log('\n❌ Conversion Errors:'); errors.slice(0, 5).forEach(({ path, error }) => { console.log(` - ${path}: ${error}`); }); if (errors.length > 5) { console.log(` ... and ${errors.length - 5} more errors`); } } if (errorCount > 0) { throw new Error(`Conversion failed for ${errorCount} files`); } return compiledFiles; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaksm/expo-docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server