const { walk, EXCLUDE_LISTS } = require('../file-walker/test'); const fs = require('fs').promises; const path_ = require('node:path'); const FILE_EXCLUDES = [ /(^|\/)\.git/, /^volatile\//, /^node_modules\//, /\/node_modules$/, /^submodules\//, /^node_modules$/, /package-lock\.json/, /^src\/dev-center\/js/, /src\/backend\/src\/public\/assets/, /^src\/gui\/src\/lib/, /^eslint\.config\.js$/, // translation readme copies /(^|\/)doc\/i18n/, // irrelevant documentation /(^|\/)doc\/graveyard/, // development logs /\/devlog\.md$/, ] const ROOT_DIR = path_.join(__dirname, '../..'); const WIKI_DIR = path_.join(__dirname, '../../submodules/wiki'); const path_to_name = path => { // Special case for Home.md if ( path === 'doc/README.md' ) return 'Home'; // Remove src/ and doc/ components // path = path.replace(/src\//g, '') path = path.replace(/doc\//g, '') // Hyphenate components path = path.replace(/-/g, '_') path = path.replace(/\//g, '-') // Remove extension path = path.replace(/\.md$/, '') return path; } const fix_relative_links = (content, entry) => { const originalDir = path_.dirname(entry); // Markdown links: [text](path/to/file.md), [text](path/to/file#section), etc return content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, link) => { // Skip external links if (link.startsWith('http://') || link.startsWith('https://') || link.startsWith('/')) { return match; } // Anchor links within the same file aren't changed if (link.startsWith('#')) return match; // Split the link to separate the path from the anchor const [linkPath, anchor] = link.split('#'); // Resolve the relative path let resolvedPath = path_.normalize(path_.join(originalDir, linkPath)); // Find the matching wiki path const wikiPath = path_to_name(resolvedPath); const newLink = anchor ? `${wikiPath}#${anchor}` : wikiPath; return `[${text}](${newLink})`; }); }; const main = async () => { const walk_iter = walk({ excludes: FILE_EXCLUDES, }, ROOT_DIR); const documents = []; for await ( const value of walk_iter ) { let path = value.path; path = path_.relative(ROOT_DIR, path); // File must be under a doc/ directory if ( ! path.match(/(^|\/)doc\//) ) continue; // File must be markdown if ( ! path.match(/\.md/) ) continue; let outputName = path_to_name(path); // Read file content let content = await fs.readFile(value.path, 'utf8'); // Get the first heading from the file to use as title const titleMatch = content.match(/^#\s+(.+)$/m); const title = titleMatch ? titleMatch[1] : outputName.replace(/-/g, ' '); // Fix internal links content = fix_relative_links(content, path); // Write the modified content to the wiki directory await fs.writeFile(path_.join(WIKI_DIR, outputName + '.md'), content); // Store information for sidebar const sidebarPath = outputName.split('-'); // The original path structure (minus doc/) helps determine the hierarchy documents.push({ sidebarPath, outputName, title: title }); } // Generate _Sidebar.md const sidebarContent = generate_sidebar(documents); await fs.writeFile(path_.join(WIKI_DIR, '_Sidebar.md'), sidebarContent); } const format_name = name => { if ( name === 'api' ) return 'API'; if ( name === 'contributors' ) return 'For Contributors'; return name.charAt(0).toUpperCase() + name.slice(1); } const generate_sidebar = (documents) => { // Sort entries by path to group related files together documents.sort((a, b) => { const pathA = a.sidebarPath.slice(0, -1).join('/'); const pathB = b.sidebarPath.slice(0, -1).join('/'); if ( pathA !== pathB ) { return pathA.localeCompare(pathB); } // README.md always goes first const isReadmeA = a.outputName.toLowerCase().includes('readme') || a.outputName.toLowerCase().includes('home'); const isReadmeB = b.outputName.toLowerCase().includes('readme') || b.outputName.toLowerCase().includes('home'); if (isReadmeA) return -1; if (isReadmeB) return 1; return a.title.localeCompare(b.title); }); // Format a document link the same way everywhere const formatDocumentLink = (document) => { let title = document.title; if ( document.outputName.split('-').slice(-1)[0].toLowerCase() === 'readme' ) { title = 'Index (README.md)'; } if ( document.outputName.split('-').slice(-1)[0].toLowerCase() === 'home' ) { title = `Home`; } return `* [${title}](${document.outputName.replace('.md', '')})\n`; }; // Recursive function to build sidebar sections const buildSection = (docs, depth = 0, prefix = '') => { let result = ''; const directDocs = []; const subSections = new Map(); // Separate direct documents from those in subsections for (const doc of docs) { if (doc.sidebarPath.length <= depth + 1) { // Direct document at this level directDocs.push(doc); } else { // Document belongs in a subsection const sectionName = doc.sidebarPath[depth]; if (!subSections.has(sectionName)) { subSections.set(sectionName, []); } subSections.get(sectionName).push(doc); } } // Add direct documents for (const doc of directDocs) { result += formatDocumentLink(doc); } // Process subsections recursively for (const [sectionName, sectionDocs] of subSections.entries()) { // Generate heading with appropriate level const headingLevel = '#'.repeat(depth + 2); const formattedName = format_name(sectionName) result += `\n${headingLevel} ${formattedName}\n`; // Process the subsection documents result += buildSection(sectionDocs, depth + 1, `${prefix}${sectionName}/`); } return result; }; // Start with the main heading let sidebar = "## General\n\n"; // Split documents into top-level and those in sections const topLevelDocs = documents.filter(doc => doc.sidebarPath.length <= 1); const sectionDocs = documents.filter(doc => doc.sidebarPath.length > 1); // Add top-level documents for (const doc of topLevelDocs) { sidebar += formatDocumentLink(doc); } // Group the remaining documents by their top-level sections const topLevelSections = new Map(); for (const doc of sectionDocs) { const sectionName = doc.sidebarPath[0]; if (!topLevelSections.has(sectionName)) { topLevelSections.set(sectionName, []); } topLevelSections.get(sectionName).push(doc); } // Process each top-level section for (const [sectionName, sectionDocs] of topLevelSections.entries()) { const formattedName = format_name(sectionName); sidebar += `\n## ${formattedName}\n`; sidebar += buildSection(sectionDocs, 1, `${sectionName}/`); } return sidebar; }; main();