UNPKG

vitepress-auto-nav

Version:

Auto-generate navigation and sidebar configuration for VitePress

395 lines (394 loc) 18.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateNav = generateNav; exports.generateSidebar = generateSidebar; exports.generateVitepressConfig = generateVitepressConfig; const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Detects the documentation directory by checking common paths */ function detectDocsDir(debug = false) { // Try common paths for docs const possiblePaths = ['src/docs', 'docs', '.']; for (const dir of possiblePaths) { const fullPath = path.resolve(process.cwd(), dir); if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) { if (debug) { console.log(`[vitepress-auto-nav] Detected documentation directory: ${dir}`); } return dir; } } // Default to docs if nothing found if (debug) { console.warn('[vitepress-auto-nav] No documentation directory detected, using "docs" as default'); } return 'docs'; } // Default options const defaultOptions = { docsDir: detectDocsDir(), defaultExpand: true, ignoreDirs: ['public', 'assets', 'node_modules'], overviewSuffix: 'Overview', formatDisplayName: (name) => { // If it's a Chinese directory, return as-is if (/[\u4e00-\u9fa5]/.test(name)) { return name; } return name.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); }, sidebarItemSorter: (a, b) => { const overviewSuffix = 'Overview'; // Put overview items at the top if (a.text && a.text.includes(overviewSuffix)) return -1; if (b.text && b.text.includes(overviewSuffix)) return 1; // Alphabetic sorting for the rest return a.text && b.text ? a.text.localeCompare(b.text) : 0; }, debug: false, maxDepth: 3, includeRootFiles: false, filePatterns: ['.md'], }; /** * Helper functions */ function isValidDir(dirPath) { try { return fs.statSync(dirPath).isDirectory(); } catch (error) { return false; } } function shouldIgnoreDir(dirName, ignoreDirs) { // Ignore directories starting with underscore and special directories return dirName.startsWith('_') || dirName.startsWith('.') || ignoreDirs.includes(dirName); } function logDebug(message, obj, debug = false) { if (!debug) return; if (obj) { console.log(`[vitepress-auto-nav] ${message}`, obj); } else { console.log(`[vitepress-auto-nav] ${message}`); } } /** * Generate VitePress navigation bar configuration * @param options Configuration options * @returns Navigation bar configuration array */ function generateNav(options = {}) { const opts = { ...defaultOptions, ...options }; const nav = []; const docsPath = path.resolve(process.cwd(), opts.docsDir); logDebug(`Generating navigation from: ${docsPath}`, null, opts.debug); try { // Check if directory exists if (!fs.existsSync(docsPath)) { console.error(`[vitepress-auto-nav] Documentation directory not found: ${docsPath}`); console.error(`[vitepress-auto-nav] Current working directory: ${process.cwd()}`); console.error(`[vitepress-auto-nav] Available directories: ${fs.readdirSync(process.cwd()).join(', ')}`); return nav; } // Loop through level 1 directories fs.readdirSync(docsPath).forEach(l1Dir => { const l1Path = path.join(docsPath, l1Dir); if (!isValidDir(l1Path) || shouldIgnoreDir(l1Dir, opts.ignoreDirs)) return; const navItem = { text: opts.formatDisplayName(l1Dir), items: [], }; if (opts.maxDepth >= 2) { // Loop through level 2 directories fs.readdirSync(l1Path).forEach(l2Dir => { const l2Path = path.join(l1Path, l2Dir); if (!isValidDir(l2Path) || shouldIgnoreDir(l2Dir, opts.ignoreDirs)) return; const l2Item = { text: opts.formatDisplayName(l2Dir), items: [], }; if (opts.maxDepth >= 3) { // Loop through level 3 directories fs.readdirSync(l2Path).forEach(l3Dir => { const l3Path = path.join(l2Path, l3Dir); if (!isValidDir(l3Path) || shouldIgnoreDir(l3Dir, opts.ignoreDirs)) return; // Check if index.md exists const indexPath = path.join(l3Path, 'index.md'); if (!fs.existsSync(indexPath)) return; if (l2Item.items) { l2Item.items.push({ text: opts.formatDisplayName(l3Dir), link: `/${l1Dir}/${l2Dir}/${l3Dir}/`, }); } }); } else { // If maxDepth is 2, add level 2 as direct link const indexPath = path.join(l2Path, 'index.md'); if (fs.existsSync(indexPath)) { l2Item.link = `/${l1Dir}/${l2Dir}/`; delete l2Item.items; } } if ((l2Item.items && l2Item.items.length > 0) || l2Item.link) { if (navItem.items) { navItem.items.push(l2Item); } } }); } else { // If maxDepth is 1, add level 1 as direct link const indexPath = path.join(l1Path, 'index.md'); if (fs.existsSync(indexPath)) { navItem.link = `/${l1Dir}/`; delete navItem.items; } } if ((navItem.items && navItem.items.length > 0) || navItem.link) { nav.push(navItem); } }); // Include root files if configured if (opts.includeRootFiles) { const rootFiles = fs.readdirSync(docsPath).filter(file => { const filePath = path.join(docsPath, file); return !fs.statSync(filePath).isDirectory() && opts.filePatterns.some(pattern => file.endsWith(pattern)); }); rootFiles.forEach(file => { // Skip index.md at root as it's typically the homepage if (file === 'index.md') return; const fileName = file.replace(/\.[^/.]+$/, ''); // Remove extension nav.push({ text: opts.formatDisplayName(fileName), link: `/${fileName}`, }); }); } logDebug('Generated navigation structure:', nav, opts.debug); } catch (error) { console.error('[vitepress-auto-nav] Error generating navigation bar:', error); } return nav; } /** * Generate VitePress sidebar configuration * @param options Configuration options * @returns Sidebar configuration object */ function generateSidebar(options = {}) { const opts = { ...defaultOptions, ...options }; const sidebar = {}; const docsPath = path.resolve(process.cwd(), opts.docsDir); logDebug(`Generating sidebar from: ${docsPath}`, null, opts.debug); try { // Check if directory exists if (!fs.existsSync(docsPath)) { console.error(`[vitepress-auto-nav] Documentation directory not found: ${docsPath}`); console.error(`[vitepress-auto-nav] Current working directory: ${process.cwd()}`); console.error(`[vitepress-auto-nav] Available directories: ${fs.readdirSync(process.cwd()).join(', ')}`); return sidebar; } // Include root files if configured if (opts.includeRootFiles) { const rootFiles = fs.readdirSync(docsPath).filter(file => { const filePath = path.join(docsPath, file); return !fs.statSync(filePath).isDirectory() && opts.filePatterns.some(pattern => file.endsWith(pattern)); }); if (rootFiles.length > 0) { sidebar['/'] = rootFiles .map(file => { const fileName = file.replace(/\.[^/.]+$/, ''); // Remove extension return { text: opts.formatDisplayName(fileName), link: fileName === 'index' ? '/' : `/${fileName}`, }; }) .sort(opts.sidebarItemSorter); } } // Process directories only if maxDepth > 0 if (opts.maxDepth > 0) { // Loop through level 1 directories fs.readdirSync(docsPath).forEach(l1Dir => { const l1Path = path.join(docsPath, l1Dir); if (!isValidDir(l1Path) || shouldIgnoreDir(l1Dir, opts.ignoreDirs)) return; // Handle level 1 files directly if maxDepth is 1 if (opts.maxDepth === 1) { const files = fs.readdirSync(l1Path).filter(file => opts.filePatterns.some(pattern => file.endsWith(pattern))); if (files.length > 0) { sidebar[`/${l1Dir}/`] = [ { text: opts.formatDisplayName(l1Dir), collapsed: opts.defaultExpand, items: files .map(file => { const fileName = file.replace(/\.[^/.]+$/, ''); // Remove extension if (fileName === 'index') { return { text: `${opts.formatDisplayName(l1Dir)} ${opts.overviewSuffix}`, link: `/${l1Dir}/`, }; } return { text: opts.formatDisplayName(fileName), link: `/${l1Dir}/${fileName}`, }; }) .sort(opts.sidebarItemSorter), }, ]; } return; } if (opts.maxDepth >= 2) { // Loop through level 2 directories fs.readdirSync(l1Path).forEach(l2Dir => { const l2Path = path.join(l1Path, l2Dir); if (!isValidDir(l2Path) || shouldIgnoreDir(l2Dir, opts.ignoreDirs)) return; // Handle level 2 files directly if maxDepth is 2 if (opts.maxDepth === 2) { const files = fs .readdirSync(l2Path) .filter(file => opts.filePatterns.some(pattern => file.endsWith(pattern))); if (files.length > 0) { sidebar[`/${l1Dir}/${l2Dir}/`] = [ { text: opts.formatDisplayName(l2Dir), collapsed: opts.defaultExpand, items: files .map(file => { const fileName = file.replace(/\.[^/.]+$/, ''); // Remove extension if (fileName === 'index') { return { text: `${opts.formatDisplayName(l2Dir)} ${opts.overviewSuffix}`, link: `/${l1Dir}/${l2Dir}/`, }; } return { text: opts.formatDisplayName(fileName), link: `/${l1Dir}/${l2Dir}/${fileName}`, }; }) .sort(opts.sidebarItemSorter), }, ]; } return; } if (opts.maxDepth >= 3) { // Loop through level 3 directories fs.readdirSync(l2Path).forEach(l3Dir => { const l3Path = path.join(l2Path, l3Dir); if (!isValidDir(l3Path) || shouldIgnoreDir(l3Dir, opts.ignoreDirs)) return; // Check if index.md exists const indexPath = path.join(l3Path, 'index.md'); if (!fs.existsSync(indexPath)) return; // Create sidebar entry for level 3 directory - preserve original case const sidebarKey = `/${l1Dir}/${l2Dir}/${l3Dir}/`; // Get all matching files in this level 3 directory const files = fs .readdirSync(l3Path) .filter(file => opts.filePatterns.some(pattern => file.endsWith(pattern))); if (files.length > 0) { sidebar[sidebarKey] = [ { text: opts.formatDisplayName(l3Dir), collapsed: opts.defaultExpand, items: files .map(file => { // Remove extension from filename const fileName = file.replace(/\.[^/.]+$/, ''); // If it's index.md, the link doesn't need to include the filename if (fileName === 'index') { return { text: `${opts.formatDisplayName(l3Dir)} ${opts.overviewSuffix}`, link: `/${l1Dir}/${l2Dir}/${l3Dir}/`, }; } return { text: opts.formatDisplayName(fileName), link: `/${l1Dir}/${l2Dir}/${l3Dir}/${fileName}`, }; }) .sort(opts.sidebarItemSorter), }, ]; } }); } }); } }); } logDebug('Generated sidebar structure:', sidebar, opts.debug); } catch (error) { console.error('[vitepress-auto-nav] Error generating sidebar:', error); } return sidebar; } /** * Automatically generate navigation bar and sidebar configuration * @param options Configuration options * @returns Object containing navigation bar and sidebar configuration */ function generateVitepressConfig(options = {}) { return { nav: generateNav(options), sidebar: generateSidebar(options), }; } // Export default configuration generator exports.default = generateVitepressConfig;