mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 03:29:02 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| #!/usr/bin/env node
 | |
| /**
 | |
|  * Fix MkDocs structure by:
 | |
|  * 1. Syncing README.md to docs/index.md with necessary path adjustments
 | |
|  * 2. Moving overview pages to index.md inside their directories to prevent duplicate navigation entries
 | |
|  */
 | |
| 
 | |
| import * as fs from 'fs';
 | |
| import * as path from 'path';
 | |
| 
 | |
| interface FixResult {
 | |
|     message: string;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Find markdown files that have a corresponding directory with the same name,
 | |
|  * and move them to index.md inside that directory.
 | |
|  */
 | |
| function fixDuplicateEntries(docsDir: string): FixResult[] {
 | |
|     const fixesMade: FixResult[] = [];
 | |
|     
 | |
|     function walkDir(dir: string): void {
 | |
|         let files: string[];
 | |
|         try {
 | |
|             files = fs.readdirSync(dir);
 | |
|         } catch (err) {
 | |
|             console.warn(`Warning: Unable to read directory ${dir}: ${err.message}`);
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         for (const file of files) {
 | |
|             const filePath = path.join(dir, file);
 | |
|             let stat: fs.Stats;
 | |
|             
 | |
|             try {
 | |
|                 stat = fs.statSync(filePath);
 | |
|             } catch (err) {
 | |
|                 // File might have been moved already, skip it
 | |
|                 continue;
 | |
|             }
 | |
|             
 | |
|             if (stat.isDirectory()) {
 | |
|                 walkDir(filePath);
 | |
|             } else if (file.endsWith('.md')) {
 | |
|                 const basename = file.slice(0, -3); // Remove .md extension
 | |
|                 const dirPath = path.join(dir, basename);
 | |
|                 
 | |
|                 // Check if there's a directory with the same name
 | |
|                 if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
 | |
|                     const indexPath = path.join(dirPath, 'index.md');
 | |
|                     
 | |
|                     // Check if index.md already exists in that directory
 | |
|                     if (!fs.existsSync(indexPath)) {
 | |
|                         // Move the file to index.md in the directory
 | |
|                         fs.renameSync(filePath, indexPath);
 | |
|                         fixesMade.push({
 | |
|                             message: `Moved ${path.relative(docsDir, filePath)} -> ${path.relative(docsDir, indexPath)}`
 | |
|                         });
 | |
|                         
 | |
|                         // Move associated images with pattern basename_*
 | |
|                         try {
 | |
|                             const dirFiles = fs.readdirSync(dir);
 | |
|                             for (const imgFile of dirFiles) {
 | |
|                                 if (imgFile.startsWith(`${basename}_`)) {
 | |
|                                     const imgSrc = path.join(dir, imgFile);
 | |
|                                     try {
 | |
|                                         if (!fs.statSync(imgSrc).isDirectory()) {
 | |
|                                             const imgDest = path.join(dirPath, imgFile);
 | |
|                                             fs.renameSync(imgSrc, imgDest);
 | |
|                                             fixesMade.push({
 | |
|                                                 message: `Moved ${path.relative(docsDir, imgSrc)} -> ${path.relative(docsDir, imgDest)}`
 | |
|                                             });
 | |
|                                         }
 | |
|                                     } catch (err) {
 | |
|                                         // File might have been moved already, skip it
 | |
|                                     }
 | |
|                                 }
 | |
|                             }
 | |
|                         } catch (err) {
 | |
|                             // Directory might not exist anymore, skip it
 | |
|                         }
 | |
|                         
 | |
|                         // Move exact match images
 | |
|                         const imgExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
 | |
|                         for (const ext of imgExtensions) {
 | |
|                             const imgFile = path.join(dir, `${basename}${ext}`);
 | |
|                             if (fs.existsSync(imgFile)) {
 | |
|                                 const imgDest = path.join(dirPath, `${basename}${ext}`);
 | |
|                                 fs.renameSync(imgFile, imgDest);
 | |
|                                 fixesMade.push({
 | |
|                                     message: `Moved ${path.relative(docsDir, imgFile)} -> ${path.relative(docsDir, imgDest)}`
 | |
|                                 });
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     walkDir(docsDir);
 | |
|     return fixesMade;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Update references in markdown files to point to the new locations.
 | |
|  */
 | |
| function updateReferences(docsDir: string): FixResult[] {
 | |
|     const updatesMade: FixResult[] = [];
 | |
|     
 | |
|     function fixLink(match: string, text: string, link: string, currentDir: string, isIndex: boolean): string {
 | |
|         // Skip external links
 | |
|         if (link.startsWith('http')) {
 | |
|             return match;
 | |
|         }
 | |
|         
 | |
|         // Decode URL-encoded paths for processing
 | |
|         let decodedLink: string;
 | |
|         try {
 | |
|             decodedLink = decodeURIComponent(link);
 | |
|         } catch (err) {
 | |
|             // If decoding fails, use the original link
 | |
|             decodedLink = link;
 | |
|         }
 | |
|         
 | |
|         // Special case: if we're in index.md and the link starts with the parent directory name
 | |
|         // This happens when a file was converted to index.md and had links to siblings
 | |
|         if (isIndex && decodedLink.includes('/')) {
 | |
|             const pathParts = decodedLink.split('/');
 | |
|             const parentDirName = path.basename(currentDir);
 | |
|             
 | |
|             // Check if first part matches the parent directory name
 | |
|             if (pathParts[0] === parentDirName) {
 | |
|                 // This is a self-referential path, strip the first part
 | |
|                 const fixedLink = pathParts.slice(1).join('/');
 | |
|                 // Re-encode spaces for URL compatibility before recursing
 | |
|                 const fixedLinkEncoded = fixedLink.replace(/ /g, '%20');
 | |
|                 // Recursively process the fixed link
 | |
|                 return fixLink(`[${text}](${fixedLinkEncoded})`, text, fixedLinkEncoded, currentDir, isIndex);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // For any .md link, check if there's a directory with index.md
 | |
|         // that should be used instead
 | |
|         if (!decodedLink.startsWith('/')) {
 | |
|             // Resolve relative to current directory
 | |
|             const resolvedPath = path.resolve(currentDir, decodedLink);
 | |
|             
 | |
|             // Check if this points to a file that should be a directory
 | |
|             // Remove .md extension to get the potential directory name
 | |
|             if (resolvedPath.endsWith('.md')) {
 | |
|                 const potentialDir = resolvedPath.slice(0, -3);
 | |
|                 const potentialIndex = path.join(potentialDir, 'index.md');
 | |
|                 
 | |
|                 // If a directory with index.md exists, update the link
 | |
|                 if (fs.existsSync(potentialIndex)) {
 | |
|                     // If we're in an index.md file and linking to a file that's now
 | |
|                     // in a sibling directory, adjust the path
 | |
|                     if (isIndex) {
 | |
|                         // Check if they share the same parent directory
 | |
|                         if (path.dirname(potentialDir) === path.dirname(currentDir)) {
 | |
|                             // It's a sibling - just use directory name
 | |
|                             const dirName = path.basename(potentialDir).replace(/ /g, '%20');
 | |
|                             return `[${text}](${dirName}/)`;
 | |
|                         }
 | |
|                     }
 | |
|                     
 | |
|                     // Calculate relative path from current file to the directory
 | |
|                     const newPath = path.relative(currentDir, potentialDir).replace(/\\/g, '/').replace(/ /g, '%20');
 | |
|                     return `[${text}](${newPath}/)`;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Also handle local references (same directory)
 | |
|         if (!decodedLink.includes('/')) {
 | |
|             const basename = decodedLink.slice(0, -3); // Remove .md extension
 | |
|             const possibleDir = path.join(currentDir, basename);
 | |
|             
 | |
|             if (fs.existsSync(possibleDir) && fs.statSync(possibleDir).isDirectory()) {
 | |
|                 // Re-encode spaces for URL compatibility
 | |
|                 const encodedBasename = basename.replace(/ /g, '%20');
 | |
|                 return `[${text}](${encodedBasename}/)`;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return match;
 | |
|     }
 | |
|     
 | |
|     function walkDir(dir: string): void {
 | |
|         let files: string[];
 | |
|         try {
 | |
|             files = fs.readdirSync(dir);
 | |
|         } catch (err) {
 | |
|             console.warn(`Warning: Unable to read directory ${dir}: ${err.message}`);
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         for (const file of files) {
 | |
|             const filePath = path.join(dir, file);
 | |
|             let stat: fs.Stats;
 | |
|             
 | |
|             try {
 | |
|                 stat = fs.statSync(filePath);
 | |
|             } catch (err) {
 | |
|                 // File might have been moved already, skip it
 | |
|                 continue;
 | |
|             }
 | |
|             
 | |
|             if (stat.isDirectory()) {
 | |
|                 walkDir(filePath);
 | |
|             } else if (file.endsWith('.md')) {
 | |
|                 let content = fs.readFileSync(filePath, 'utf-8');
 | |
|                 const originalContent = content;
 | |
|                 
 | |
|                 const isIndex = file === 'index.md';
 | |
|                 const currentDir = path.dirname(filePath);
 | |
|                 
 | |
|                 // Update markdown links: [text](path.md)
 | |
|                 const pattern = /\[([^\]]*)\]\(([^)]+\.md)\)/g;
 | |
|                 content = content.replace(pattern, (match, text, link) => {
 | |
|                     return fixLink(match, text, link, currentDir, isIndex);
 | |
|                 });
 | |
|                 
 | |
|                 if (content !== originalContent) {
 | |
|                     fs.writeFileSync(filePath, content, 'utf-8');
 | |
|                     updatesMade.push({
 | |
|                         message: `Updated references in ${path.relative(docsDir, filePath)}`
 | |
|                     });
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     walkDir(docsDir);
 | |
|     return updatesMade;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sync README.md to docs/index.md with necessary path adjustments
 | |
|  */
 | |
| function syncReadmeToIndex(projectRoot: string, docsDir: string): FixResult[] {
 | |
|     const results: FixResult[] = [];
 | |
|     const readmePath = path.join(projectRoot, 'README.md');
 | |
|     const indexPath = path.join(docsDir, 'index.md');
 | |
|     
 | |
|     if (!fs.existsSync(readmePath)) {
 | |
|         console.warn('README.md not found in project root');
 | |
|         return results;
 | |
|     }
 | |
|     
 | |
|     // Read README content
 | |
|     let content = fs.readFileSync(readmePath, 'utf-8');
 | |
|     
 | |
|     // Fix image path (./docs/app.png -> app.png)
 | |
|     content = content.replace(/src="\.\/docs\/app\.png"/g, 'src="app.png"');
 | |
|     
 | |
|     // Fix language links in header
 | |
|     content = content.replace(/\[English\]\(\.\/README\.md\)/g, '[English](./index.md)');
 | |
|     content = content.replace(/\.\/docs\/README-ZH_CN\.md/g, './README-ZH_CN.md');
 | |
|     content = content.replace(/\.\/docs\/README-ZH_TW\.md/g, './README-ZH_TW.md');
 | |
|     content = content.replace(/\.\/docs\/README\.ru\.md/g, './README.ru.md');
 | |
|     content = content.replace(/\.\/docs\/README\.ja\.md/g, './README.ja.md');
 | |
|     content = content.replace(/\.\/docs\/README\.it\.md/g, './README.it.md');
 | |
|     content = content.replace(/\.\/docs\/README\.es\.md/g, './README.es.md');
 | |
|     
 | |
|     // Fix internal documentation links (./docs/User%20Guide -> ./User%20Guide)
 | |
|     content = content.replace(/\.\/docs\/User%20Guide/g, './User%20Guide');
 | |
|     
 | |
|     // Write the adjusted content to docs/index.md
 | |
|     fs.writeFileSync(indexPath, content, 'utf-8');
 | |
|     results.push({
 | |
|         message: `Synced README.md to docs/index.md with path adjustments`
 | |
|     });
 | |
|     
 | |
|     return results;
 | |
| }
 | |
| 
 | |
| function main(): number {
 | |
|     // Get the docs directory
 | |
|     const scriptDir = path.dirname(new URL(import.meta.url).pathname);
 | |
|     const projectRoot = path.dirname(scriptDir);
 | |
|     const docsDir = path.join(projectRoot, 'docs');
 | |
|     
 | |
|     // Handle Windows paths (remove leading slash if on Windows)
 | |
|     const normalizedProjectRoot = process.platform === 'win32' && projectRoot.startsWith('/') 
 | |
|         ? projectRoot.substring(1) 
 | |
|         : projectRoot;
 | |
|     const normalizedDocsDir = process.platform === 'win32' && docsDir.startsWith('/') 
 | |
|         ? docsDir.substring(1) 
 | |
|         : docsDir;
 | |
|     
 | |
|     if (!fs.existsSync(normalizedDocsDir)) {
 | |
|         console.error(`Error: docs directory not found at ${normalizedDocsDir}`);
 | |
|         return 1;
 | |
|     }
 | |
|     
 | |
|     console.log(`Fixing MkDocs structure in ${normalizedDocsDir}`);
 | |
|     console.log('-'.repeat(50));
 | |
|     
 | |
|     // Sync README.md to docs/index.md
 | |
|     const syncResults = syncReadmeToIndex(normalizedProjectRoot, normalizedDocsDir);
 | |
|     if (syncResults.length > 0) {
 | |
|         console.log('README sync:');
 | |
|         for (const result of syncResults) {
 | |
|             console.log(`  - ${result.message}`);
 | |
|         }
 | |
|         console.log();
 | |
|     }
 | |
|     
 | |
|     // Fix duplicate entries
 | |
|     const fixes = fixDuplicateEntries(normalizedDocsDir);
 | |
|     if (fixes.length > 0) {
 | |
|         console.log('Files reorganized:');
 | |
|         for (const fix of fixes) {
 | |
|             console.log(`  - ${fix.message}`);
 | |
|         }
 | |
|     } else {
 | |
|         console.log('No duplicate entries found that need fixing');
 | |
|     }
 | |
|     
 | |
|     console.log();
 | |
|     
 | |
|     // Update references
 | |
|     const updates = updateReferences(normalizedDocsDir);
 | |
|     if (updates.length > 0) {
 | |
|         console.log('References updated:');
 | |
|         for (const update of updates) {
 | |
|             console.log(`  - ${update.message}`);
 | |
|         }
 | |
|     } else {
 | |
|         console.log('No references needed updating');
 | |
|     }
 | |
|     
 | |
|     console.log('-'.repeat(50));
 | |
|     console.log(`Structure fix complete: ${syncResults.length} README syncs, ${fixes.length} files moved, ${updates.length} files updated`);
 | |
|     
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // Run the main function
 | |
| process.exit(main()); | 
