import fs from 'fs'; import { glob } from 'glob'; import yaml from 'js-yaml'; import path from 'path'; import { fileURLToPath } from 'url'; // Get __dirname equivalent in ESM const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configure paths const TEMPLATE_DIR = path.resolve(__dirname, '../templates'); const OUTPUT_BASE_DIR = path.resolve(__dirname, '../content/docs'); const CONFIG_FILE = path.resolve(__dirname, '../docs-platform.yml'); const PLATFORMS = ['next', 'react', 'js', 'python']; // Platform groups mapping const PLATFORM_GROUPS = { 'react-like': ['next', 'react'], // Platforms that use React components 'js-like': ['next', 'react', 'js'] // Platforms that use JavaScript SDK (includes React-based platforms) }; // Load platform configuration let platformConfig = {}; try { const configContent = fs.readFileSync(CONFIG_FILE, 'utf8'); platformConfig = yaml.load(configContent); console.log('Loaded platform configuration from docs-platform.yml'); } catch (error) { console.error('Failed to load platform configuration:', error.message); console.log('Falling back to include all files for all platforms'); } // Platform folder naming - now using root folders function getFolderName(platform) { return platform; // Use direct platform names instead of pages-{platform} } // Platform display names function getPlatformDisplayName(platform) { const platformNames = { 'next': 'Next.js', 'react': 'React', 'js': 'JavaScript', 'python': 'Python' }; return platformNames[platform] || platform; } // Platform-specific content markers - Updated regex to handle both syntaxes (with and without colon) const PLATFORM_START_MARKER = /{\s*\/\*\s*IF_PLATFORM:?\s*([\w-]+)\s*\*\/\s*}/; const PLATFORM_ELSE_MARKER = /{\s*\/\*\s*ELSE_IF_PLATFORM:?\s+([\w-]+)\s*\*\/\s*}/; const PLATFORM_END_MARKER = /{\s*\/\*\s*END_PLATFORM\s*\*\/\s*}/; /** * Check if a platform or platform group includes the target platform */ function isPlatformMatch(platformSpec, targetPlatform) { // Direct platform match if (platformSpec === targetPlatform) { return true; } // Platform group match if (PLATFORM_GROUPS[platformSpec]) { return PLATFORM_GROUPS[platformSpec].includes(targetPlatform); } return false; } /** * Check if a file should be included for a specific platform */ function shouldIncludeFileForPlatform(platform, filePath) { // If no configuration loaded, include everything if (!platformConfig.pages) { return true; } // Find the page configuration for this file const pageConfig = platformConfig.pages.find(page => page.path === filePath); // If no specific configuration found, exclude by default if (!pageConfig) { console.log(`No configuration found for ${filePath}, excluding by default`); return false; } // Check if the platform is in the allowed list return pageConfig.platforms.includes(platform); } /** * Process a template file for a specific platform */ function processTemplateForPlatform(content, targetPlatform) { const lines = content.split('\n'); let result = []; let currentPlatformSpec = null; let isIncluding = true; let platformSection = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Check for platform start const startMatch = line.match(PLATFORM_START_MARKER); if (startMatch) { platformSection = true; currentPlatformSpec = startMatch[1]; isIncluding = isPlatformMatch(currentPlatformSpec, targetPlatform); continue; } // Check for platform else const elseMatch = line.match(PLATFORM_ELSE_MARKER); if (elseMatch && platformSection) { currentPlatformSpec = elseMatch[1]; isIncluding = isPlatformMatch(currentPlatformSpec, targetPlatform); continue; } // Check for platform end const endMatch = line.match(PLATFORM_END_MARKER); if (endMatch && platformSection) { platformSection = false; isIncluding = true; continue; } // Include the line if we're supposed to if (isIncluding) { result.push(line); } } return result.join('\n'); } /** * Generate meta.json files for Fumadocs navigation */ function generateMetaFiles() { // Process meta.json files for each platform from templates for (const platform of PLATFORMS) { const folderName = getFolderName(platform); const platformDisplayName = getPlatformDisplayName(platform); // Find all meta.json files in the template directory const metaFiles = glob.sync('**/meta.json', { cwd: TEMPLATE_DIR }); for (const metaFile of metaFiles) { const srcPath = path.join(TEMPLATE_DIR, metaFile); const destPath = path.join(OUTPUT_BASE_DIR, folderName, metaFile); // If this is a nested meta.json (not root), check if the folder should exist for this platform if (metaFile !== 'meta.json') { const folderPath = path.dirname(metaFile); // Check if any pages in this folder are included for this platform const hasContentInFolder = platformConfig.pages && platformConfig.pages.some(configPage => configPage.path.startsWith(`${folderPath}/`) && configPage.platforms.includes(platform) ); if (!hasContentInFolder) { console.log(`Skipped meta.json for ${folderPath} (no content for ${platform})`); continue; // Skip this meta.json file } } // Read and parse the template meta.json const templateContent = fs.readFileSync(srcPath, 'utf8'); const metaData = JSON.parse(templateContent); // If this is the root meta.json, customize it for the platform if (metaFile === 'meta.json') { metaData.title = platformDisplayName; metaData.description = `Stack Auth for ${platformDisplayName} applications`; metaData.root = true; // Filter pages based on platform configuration if (platformConfig.pages && metaData.pages) { const cleanedPages = []; let currentSectionPages = []; let currentSectionHeader = null; for (let i = 0; i < metaData.pages.length; i++) { const page = metaData.pages[i]; // If this is a section divider if (typeof page === 'string' && page.startsWith('---')) { // Process the previous section first (or pages before first section) if (currentSectionPages.length > 0) { if (currentSectionHeader !== null) { // Add section header if we had one cleanedPages.push(currentSectionHeader); } cleanedPages.push(...currentSectionPages); } // Start new section currentSectionHeader = page; currentSectionPages = []; } // If this is a folder reference (like "...customization") else if (typeof page === 'string' && page.startsWith('...')) { // Only include folder references if they have content for this platform const folderName = page.substring(3); // Remove "..." const hasContentInFolder = platformConfig.pages.some(configPage => configPage.path.startsWith(`${folderName}/`) && configPage.platforms.includes(platform) ); if (hasContentInFolder) { currentSectionPages.push(page); } } // Regular page else { // Check if this is actually a folder reference vs a page reference // A folder reference should have a corresponding directory in templates const folderPath = path.join(TEMPLATE_DIR, page); const isActualFolder = fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory(); if (isActualFolder) { // This is a folder reference - check if folder has content for this platform const hasContentInFolder = platformConfig.pages.some(configPage => configPage.path.startsWith(`${page}/`) && configPage.platforms.includes(platform) ); if (hasContentInFolder) { currentSectionPages.push(page); } } else { // This is a regular page reference const pagePath = `${page}.mdx`; const shouldInclude = shouldIncludeFileForPlatform(platform, pagePath); if (shouldInclude) { currentSectionPages.push(page); } } } } // Don't forget the last section (or remaining pages) if (currentSectionPages.length > 0) { if (currentSectionHeader !== null) { cleanedPages.push(currentSectionHeader); } cleanedPages.push(...currentSectionPages); } metaData.pages = cleanedPages; } } // Create directory if it doesn't exist fs.mkdirSync(path.dirname(destPath), { recursive: true }); // Write the processed meta.json fs.writeFileSync(destPath, JSON.stringify(metaData, null, 2)); console.log(`Generated platform-specific meta.json for ${platform}: ${destPath}`); } } } /** * Copy assets from template to platform-specific directories */ function copyAssets() { const assetDirs = ['imgs']; for (const dir of assetDirs) { const srcDir = path.join(TEMPLATE_DIR, dir); if (fs.existsSync(srcDir)) { // Copy assets to each platform directory for (const platform of PLATFORMS) { const folderName = getFolderName(platform); const destDir = path.join(OUTPUT_BASE_DIR, folderName, dir); fs.mkdirSync(destDir, { recursive: true }); // Find and copy all files const files = glob.sync('**/*', { cwd: srcDir, nodir: true }); for (const file of files) { const srcFile = path.join(srcDir, file); const destFile = path.join(destDir, file); fs.mkdirSync(path.dirname(destFile), { recursive: true }); fs.copyFileSync(srcFile, destFile); console.log(`Copied asset: ${srcFile} -> ${destFile}`); } } } } } /** * Main function to generate platform-specific docs */ function generateDocs() { // Find all MDX files in the template directory const templateFiles = glob.sync('**/*.mdx', { cwd: TEMPLATE_DIR }); if (templateFiles.length === 0) { console.warn(`No template files found in ${TEMPLATE_DIR}`); return; } console.log(`Found ${templateFiles.length} template files`); // Process for each platform for (const platform of PLATFORMS) { const folderName = getFolderName(platform); const outputDir = path.join(OUTPUT_BASE_DIR, folderName); // Create the output directory fs.mkdirSync(outputDir, { recursive: true }); // Process each template file for (const file of templateFiles) { // Check if this file should be included for this platform if (!shouldIncludeFileForPlatform(platform, file)) { console.log(`Skipped file (not configured for platform): ${file} for ${platform}`); continue; } const inputFile = path.join(TEMPLATE_DIR, file); const outputFile = path.join(outputDir, file); // Read the template const templateContent = fs.readFileSync(inputFile, 'utf8'); // Process for this platform const processedContent = processTemplateForPlatform(templateContent, platform); // Create output directory if it doesn't exist fs.mkdirSync(path.dirname(outputFile), { recursive: true }); // Write the processed content fs.writeFileSync(outputFile, processedContent); console.log(`Generated: ${outputFile}`); } } // Generate meta.json files for navigation generateMetaFiles(); // Copy assets (images, etc.) copyAssets(); console.log('Documentation generation complete!'); } // Run the generator generateDocs();