mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	feat(docs): transition from python to ts
This commit is contained in:
		
							
								
								
									
										37
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,6 +14,7 @@ on: | ||||
|       - 'mkdocs.yml' | ||||
|       - 'requirements-docs.txt' | ||||
|       - '.github/workflows/deploy-docs.yml' | ||||
|       - 'scripts/fix-mkdocs-structure.ts' | ||||
|    | ||||
|   # Allow manual triggering from Actions tab | ||||
|   workflow_dispatch: | ||||
| @@ -28,6 +29,7 @@ on: | ||||
|       - 'mkdocs.yml' | ||||
|       - 'requirements-docs.txt' | ||||
|       - '.github/workflows/deploy-docs.yml' | ||||
|       - 'scripts/fix-mkdocs-structure.ts' | ||||
|  | ||||
| jobs: | ||||
|   build-and-deploy: | ||||
| @@ -62,10 +64,26 @@ jobs: | ||||
|         env: | ||||
|           PIP_DISABLE_PIP_VERSION_CHECK: 1 | ||||
|        | ||||
|       # Setup pnpm before fixing docs structure | ||||
|       - name: Setup pnpm | ||||
|         uses: pnpm/action-setup@v4 | ||||
|        | ||||
|       # Setup Node.js with pnpm | ||||
|       - name: Setup Node.js | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version: '20' | ||||
|           cache: 'pnpm' | ||||
|        | ||||
|       # Install Node.js dependencies for the TypeScript script | ||||
|       - name: Install Dependencies | ||||
|         run: | | ||||
|           pnpm install --frozen-lockfile | ||||
|        | ||||
|       - name: Fix Documentation Structure | ||||
|         run: | | ||||
|           # Fix duplicate navigation entries by moving overview pages to index.md | ||||
|           python scripts/fix-mkdocs-structure.py | ||||
|           pnpm run chore:fix-mkdocs-structure | ||||
|        | ||||
|       - name: Build MkDocs Site | ||||
|         run: | | ||||
| @@ -91,17 +109,6 @@ jobs: | ||||
|           test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1) | ||||
|           echo "✅ Site validation passed" | ||||
|        | ||||
|       # Setup pnpm  | ||||
|       - name: Setup pnpm | ||||
|         uses: pnpm/action-setup@v4 | ||||
|        | ||||
|       # Setup Node.js with pnpm | ||||
|       - name: Setup Node.js | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version: '20' | ||||
|           cache: 'pnpm' | ||||
|        | ||||
|       # Install wrangler globally to avoid workspace issues | ||||
|       - name: Install Wrangler | ||||
|         run: | | ||||
| @@ -115,7 +122,7 @@ jobs: | ||||
|         with: | ||||
|           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||
|           accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||
|           command: pages deploy site --project-name=triliumnext-pages --branch=${{ github.ref_name }} | ||||
|           command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }} | ||||
|           wranglerVersion: ''  # Use pre-installed version | ||||
|        | ||||
|       # Deploy preview for PRs | ||||
| @@ -126,7 +133,7 @@ jobs: | ||||
|         with: | ||||
|           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||
|           accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||
|           command: pages deploy site --project-name=triliumnext-pages --branch=pr-${{ github.event.pull_request.number }} | ||||
|           command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }} | ||||
|           wranglerVersion: ''  # Use pre-installed version | ||||
|        | ||||
|       # Post deployment URL as PR comment | ||||
| @@ -139,7 +146,7 @@ jobs: | ||||
|             const prNumber = context.issue.number; | ||||
|             // Construct preview URL based on Cloudflare Pages pattern | ||||
|             const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`; | ||||
|             const mainUrl = 'https://docs.trilium.app'; | ||||
|             const mainUrl = 'https://docs.triliumnotes.org'; | ||||
|              | ||||
|             // Check if we already commented | ||||
|             const comments = await github.rest.issues.listComments({ | ||||
|   | ||||
							
								
								
									
										308
									
								
								scripts/fix-mkdocs-structure.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								scripts/fix-mkdocs-structure.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,308 @@ | ||||
| #!/usr/bin/env node | ||||
| /** | ||||
|  * Fix MkDocs structure by moving overview pages to index.md inside their directories. | ||||
|  * This prevents duplicate navigation entries when a file and directory have the same name. | ||||
|  */ | ||||
|  | ||||
| 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 | ||||
|         // Use decodeURIComponent which is equivalent to Python's unquote | ||||
|         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 | ||||
|         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('/'); | ||||
|                 // Continue processing with the fixed link | ||||
|                 const decodedFixedLink = fixedLink; | ||||
|                  | ||||
|                 // Check if this fixed link points to a directory with index.md | ||||
|                 if (!decodedFixedLink.startsWith('/')) { | ||||
|                     const resolvedPath = path.resolve(currentDir, decodedFixedLink); | ||||
|                      | ||||
|                     if (resolvedPath.endsWith('.md')) { | ||||
|                         const potentialDir = resolvedPath.slice(0, -3); | ||||
|                         const potentialIndex = path.join(potentialDir, 'index.md'); | ||||
|                          | ||||
|                         if (fs.existsSync(potentialIndex)) { | ||||
|                             // 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}/)`; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                 // If no special handling needed for the fixed link, return it as-is | ||||
|                 const fixedLinkEncoded = fixedLink.replace(/ /g, '%20'); | ||||
|                 return `[${text}](${fixedLinkEncoded})`; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // For any .md link, check if there's a directory with index.md | ||||
|         if (!decodedLink.startsWith('/')) { | ||||
|             const resolvedPath = path.resolve(currentDir, decodedLink); | ||||
|              | ||||
|             // Check if this points to a file that should be a directory | ||||
|             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 (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) - should be 'if', not 'elif' | ||||
|         // This is intentional to handle both absolute and relative paths | ||||
|         if (!decodedLink.includes('/')) { | ||||
|             const basename = decodedLink.slice(0, -3); // Remove .md | ||||
|             const possibleDir = path.join(currentDir, basename); | ||||
|              | ||||
|             if (fs.existsSync(possibleDir) && fs.statSync(possibleDir).isDirectory()) { | ||||
|                 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; | ||||
| } | ||||
|  | ||||
| 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 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)); | ||||
|      | ||||
|     // 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: ${fixes.length} files moved, ${updates.length} files updated`); | ||||
|      | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| // Run the main function | ||||
| process.exit(main()); | ||||
		Reference in New Issue
	
	Block a user