import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Define the NavItem interface to match the existing type interface NavItem { label: string; link: string; children?: NavItem[]; } const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const docsDir = path.resolve(__dirname, '../src/docs'); const outputFile = path.resolve(__dirname, '../src/data/navData.json'); function toTitleCase(str: string): string { return str .replace(/[-_]/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } function getBaseName(filePath: string): string { return path.basename(filePath, path.extname(filePath)); } function isHidden(name: string): boolean { return name.startsWith('.'); } /** * Recursively checks if any markdown file exists in a directory or its subdirectories */ function hasMarkdownFilesDeep(dirPath: string): boolean { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (isHidden(entry.name)) continue; if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') { return true; } else if (entry.isDirectory()) { if (hasMarkdownFilesDeep(fullPath)) return true; } } return false; } /** * Recursively scans a directory and builds a navigation structure */ function scanDirectory(dirPath: string, relativePath: string = ''): NavItem[] { const items: NavItem[] = []; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); const directories = entries.filter(entry => entry.isDirectory() && !isHidden(entry.name)); for (const dir of directories) { const dirRelativePath = path.join(relativePath, dir.name); const dirFullPath = path.join(dirPath, dir.name); if (!hasMarkdownFilesDeep(dirFullPath)) { continue; // Skip folders that don't contain markdown files even in subdirs } const navItem: NavItem = { label: toTitleCase(dir.name), link: '/' + dirRelativePath, children: scanDirectory(dirFullPath, dirRelativePath) }; items.push(navItem); } const files = entries.filter(entry => entry.isFile() && !isHidden(entry.name) && path.extname(entry.name).toLowerCase() === '.md' ); for (const file of files) { const baseName = getBaseName(file.name); const fileRelativePath = path.join(relativePath, baseName); const navItem: NavItem = { label: toTitleCase(baseName), link: '/' + fileRelativePath }; items.push(navItem); } return items; } function generateNavData(): void { try { console.log('Generating navigation data...'); const topLevelDirs = fs.readdirSync(docsDir, { withFileTypes: true }) .filter(entry => entry.isDirectory() && !isHidden(entry.name)); const navData: NavItem[] = []; for (const dir of topLevelDirs) { const dirPath = path.join(docsDir, dir.name); if (!hasMarkdownFilesDeep(dirPath)) { continue; } const children = scanDirectory(dirPath, dir.name); const navItem: NavItem = { label: toTitleCase(dir.name), link: '/' + dir.name, children }; navData.push(navItem); } fs.writeFileSync(outputFile, JSON.stringify(navData, null, 2)); console.log(`Navigation data generated successfully: ${outputFile}`); } catch (error) { console.error('Error generating navigation data:', error); process.exit(1); } } // Run the generator generateNavData();