#!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; import { glob } from 'glob'; interface LinkRewriteResult { filePath: string; originalLinks: string[]; rewrittenLinks: string[]; brokenLinks: string[]; } class LinkRewriter { private docsRoot: string; private pathMapping: Record = {}; private brokenLinks: string[] = []; constructor(docsRoot: string) { this.docsRoot = docsRoot; this.loadPathMapping(); } private loadPathMapping(): void { const pathMapPath = path.join(this.docsRoot, '_reports', 'path_map.json'); if (fs.existsSync(pathMapPath)) { this.pathMapping = JSON.parse(fs.readFileSync(pathMapPath, 'utf-8')); } else { this.pathMapping = {}; console.warn('⚠️ Aucun mapping de chemins trouvé. Les liens ne seront pas réécrits.'); } } async rewriteAllLinks(): Promise { console.log('🔗 Réécriture des liens...'); const patterns = [ 'current/**/*.md', 'vision/**/*.md', 'current/**/*.mdx', 'vision/**/*.mdx' ]; const allFiles: string[] = []; for (const pattern of patterns) { const files = await glob(pattern, { cwd: this.docsRoot }); allFiles.push(...files); } console.log(`📄 ${allFiles.length} fichiers à traiter`); for (const file of allFiles) { await this.rewriteFileLinks(file); } this.generateLinkReport(); } private async rewriteFileLinks(relativePath: string): Promise { const fullPath = path.join(this.docsRoot, relativePath); const content = fs.readFileSync(fullPath, 'utf-8'); const result = this.rewriteContentLinks(content, relativePath); if (result.rewrittenLinks.length > 0 || result.brokenLinks.length > 0) { fs.writeFileSync(fullPath, result.rewrittenLinks.join('')); console.log(`✅ ${relativePath}: ${result.rewrittenLinks.length} liens réécrits, ${result.brokenLinks.length} liens brisés`); } } private rewriteContentLinks(content: string, filePath: string): LinkRewriteResult { const originalLinks: string[] = []; const rewrittenLinks: string[] = []; const brokenLinks: string[] = []; // Regex pour capturer les liens Markdown const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g; let match; while ((match = linkRegex.exec(content)) !== null) { const [fullMatch, linkText, linkUrl] = match; originalLinks.push(fullMatch); const rewrittenLink = this.rewriteLink(linkUrl, filePath); if (rewrittenLink) { rewrittenLinks.push(fullMatch.replace(linkUrl, rewrittenLink)); } else { brokenLinks.push(linkUrl); rewrittenLinks.push(fullMatch); // Garder le lien original } } return { filePath, originalLinks, rewrittenLinks, brokenLinks }; } private rewriteLink(linkUrl: string, currentFilePath: string): string | null { // Ignorer les liens externes if (linkUrl.startsWith('http://') || linkUrl.startsWith('https://') || linkUrl.startsWith('mailto:')) { return linkUrl; } // Ignorer les ancres if (linkUrl.startsWith('#')) { return linkUrl; } // Gérer les liens vers des images if (this.isImageLink(linkUrl)) { return this.rewriteImageLink(linkUrl, currentFilePath); } // Gérer les liens vers des documents return this.rewriteDocumentLink(linkUrl, currentFilePath); } private isImageLink(url: string): boolean { const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp']; return imageExtensions.some(ext => url.toLowerCase().includes(ext)); } private rewriteImageLink(linkUrl: string, currentFilePath: string): string { // Si c'est déjà un chemin vers assets, le garder if (linkUrl.startsWith('../assets/') || linkUrl.startsWith('/assets/')) { return linkUrl; } // Sinon, pointer vers le dossier assets const fileName = path.basename(linkUrl); return `../assets/${fileName}`; } private rewriteDocumentLink(linkUrl: string, currentFilePath: string): string | null { // Résoudre le chemin relatif const currentDir = path.dirname(currentFilePath); const resolvedPath = path.resolve(this.docsRoot, currentDir, linkUrl); const relativePath = path.relative(this.docsRoot, resolvedPath); // Chercher dans le mapping for (const [originalPath, newPath] of Object.entries(this.pathMapping)) { if (originalPath === relativePath || originalPath.endsWith(relativePath)) { return newPath; } } // Si pas trouvé, essayer de deviner le nouveau chemin return this.guessNewPath(linkUrl, currentFilePath); } private guessNewPath(linkUrl: string, currentFilePath: string): string | null { // Logique simple pour deviner le nouveau chemin // Cette fonction peut être améliorée selon les besoins const fileName = path.basename(linkUrl, path.extname(linkUrl)); const slug = fileName .toLowerCase() .replace(/[^a-z0-9-]/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); // Déterminer la piste (current/vision) basée sur le fichier actuel const track = currentFilePath.startsWith('current/') ? 'current' : 'vision'; // Déterminer le domaine basé sur le chemin const domain = this.guessDomainFromPath(linkUrl); return `/${track}/domains/${domain}/${slug}`; } private guessDomainFromPath(linkUrl: string): string { const pathLower = linkUrl.toLowerCase(); if (pathLower.includes('backend') || pathLower.includes('api')) return 'backend'; if (pathLower.includes('frontend') || pathLower.includes('ui')) return 'frontend'; if (pathLower.includes('rust') || pathLower.includes('chat')) return 'rust'; if (pathLower.includes('infra') || pathLower.includes('docker')) return 'infra'; if (pathLower.includes('security') || pathLower.includes('auth')) return 'security'; if (pathLower.includes('ops') || pathLower.includes('monitoring')) return 'ops'; if (pathLower.includes('product') || pathLower.includes('ux')) return 'product'; if (pathLower.includes('legal')) return 'legal'; return 'misc'; } private generateLinkReport(): void { const reportPath = path.join(this.docsRoot, '_reports', 'link_rewrite_report.md'); const reportContent = `# Rapport de Réécriture des Liens ## Résumé - **Liens brisés détectés**: ${this.brokenLinks.length} ## Liens brisés ${this.brokenLinks.length > 0 ? this.brokenLinks.map(link => `- ${link}`).join('\n') : 'Aucun lien brisé détecté'} ## Notes - Les liens externes (http/https) ont été préservés - Les liens vers les images ont été redirigés vers le dossier assets - Les liens vers les documents ont été mappés selon le nouveau système de chemins `; fs.writeFileSync(reportPath, reportContent); console.log(`📋 Rapport de liens généré: ${reportPath}`); } async run(): Promise { console.log('🔗 Démarrage de la réécriture des liens...'); try { await this.rewriteAllLinks(); console.log('✅ Réécriture des liens terminée!'); } catch (error) { console.error('❌ Erreur lors de la réécriture des liens:', error); process.exit(1); } } } // Exécution du script if (require.main === module) { const docsRoot = process.argv[2] || path.join(__dirname, '..'); const rewriter = new LinkRewriter(docsRoot); rewriter.run(); } export { LinkRewriter };