#!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; import { glob } from 'glob'; interface DocFile { path: string; size: number; track: 'current' | 'vision' | 'unknown'; domain: 'backend' | 'frontend' | 'rust' | 'infra' | 'security' | 'ops' | 'product' | 'legal' | 'misc'; status: 'in_veza_docs' | 'legacy' | 'excluded'; relativePath: string; isImage: boolean; linkedImages?: string[]; } class GlobalDocScanner { private repoRoot: string; private vezaDocsRoot: string; private results: DocFile[] = []; constructor(repoRoot: string) { this.repoRoot = repoRoot; this.vezaDocsRoot = path.join(repoRoot, 'veza-docs'); } async scanAll(): Promise { console.log('🔍 Scan global de tous les fichiers de documentation...'); // Patterns à scanner const patterns = [ '**/*.md', '**/*.mdx', '**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.svg', '**/*.gif', '**/*.webp' ]; // Dossiers à exclure const excludePatterns = [ 'node_modules/**', 'dist/**', 'build/**', 'vendor/**', 'target/**', '.git/**', '.docusaurus/**', 'coverage/**', '.nyc_output/**', '*.log', '*.pid', '.env*', '*.key', '*.pem', '*.p12', '*.pfx' ]; const allFiles: string[] = []; for (const pattern of patterns) { const files = await glob(pattern, { cwd: this.repoRoot, ignore: excludePatterns }); allFiles.push(...files); } console.log(`📄 ${allFiles.length} fichiers trouvés`); for (const file of allFiles) { const fullPath = path.join(this.repoRoot, file); const stats = fs.statSync(fullPath); const docFile: DocFile = { path: file, size: stats.size, track: 'unknown', domain: 'misc', status: 'legacy', relativePath: file, isImage: this.isImageFile(file) }; // Déterminer si le fichier est dans veza-docs if (file.startsWith('veza-docs/')) { docFile.status = 'in_veza_docs'; docFile.track = this.determineTrack(file); docFile.domain = this.determineDomain(file); } else if (this.isExcluded(file)) { docFile.status = 'excluded'; } // Pour les fichiers Markdown, chercher les images liées if (!docFile.isImage && (file.endsWith('.md') || file.endsWith('.mdx'))) { docFile.linkedImages = this.findLinkedImages(fullPath); } this.results.push(docFile); } console.log(`✅ Scan terminé: ${this.results.length} fichiers analysés`); } private isImageFile(filePath: string): boolean { const ext = path.extname(filePath).toLowerCase(); return ['.png', '.jpg', '.jpeg', '.svg', '.gif', '.webp'].includes(ext); } private isExcluded(filePath: string): boolean { const excludeDirs = [ 'node_modules', 'dist', 'build', 'vendor', 'target', '.git', '.docusaurus', 'coverage', '.nyc_output' ]; return excludeDirs.some(dir => filePath.includes(`/${dir}/`) || filePath.startsWith(`${dir}/`)); } private determineTrack(filePath: string): 'current' | 'vision' | 'unknown' { if (filePath.includes('/current/')) return 'current'; if (filePath.includes('/vision/')) return 'vision'; return 'unknown'; } private determineDomain(filePath: string): 'backend' | 'frontend' | 'rust' | 'infra' | 'security' | 'ops' | 'product' | 'legal' | 'misc' { const pathLower = filePath.toLowerCase(); if (pathLower.includes('/backend/')) return 'backend'; if (pathLower.includes('/frontend/')) return 'frontend'; if (pathLower.includes('/rust/')) return 'rust'; if (pathLower.includes('/infra/')) return 'infra'; if (pathLower.includes('/security/')) return 'security'; if (pathLower.includes('/ops/')) return 'ops'; if (pathLower.includes('/product/')) return 'product'; if (pathLower.includes('/legal/')) return 'legal'; return 'misc'; } private findLinkedImages(filePath: string): string[] { try { const content = fs.readFileSync(filePath, 'utf-8'); const imageRegex = /!\[.*?\]\((.*?)\)/g; const images: string[] = []; let match; while ((match = imageRegex.exec(content)) !== null) { const imagePath = match[1]; if (imagePath && !imagePath.startsWith('http')) { images.push(imagePath); } } return images; } catch (error) { return []; } } async generateReports(): Promise { console.log('📊 Génération des rapports...'); // Rapport JSON const jsonReport = { scanDate: new Date().toISOString(), totalFiles: this.results.length, inVezaDocs: this.results.filter(f => f.status === 'in_veza_docs').length, legacy: this.results.filter(f => f.status === 'legacy').length, excluded: this.results.filter(f => f.status === 'excluded').length, byTrack: { current: this.results.filter(f => f.track === 'current').length, vision: this.results.filter(f => f.track === 'vision').length, unknown: this.results.filter(f => f.track === 'unknown').length }, byDomain: { backend: this.results.filter(f => f.domain === 'backend').length, frontend: this.results.filter(f => f.domain === 'frontend').length, rust: this.results.filter(f => f.domain === 'rust').length, infra: this.results.filter(f => f.domain === 'infra').length, security: this.results.filter(f => f.domain === 'security').length, ops: this.results.filter(f => f.domain === 'ops').length, product: this.results.filter(f => f.domain === 'product').length, legal: this.results.filter(f => f.domain === 'legal').length, misc: this.results.filter(f => f.domain === 'misc').length }, files: this.results }; const reportsDir = path.join(this.vezaDocsRoot, '_reports'); if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir, { recursive: true }); } fs.writeFileSync( path.join(reportsDir, 'global_scan.json'), JSON.stringify(jsonReport, null, 2) ); // Rapport Markdown const mdReport = this.generateMarkdownReport(jsonReport); fs.writeFileSync( path.join(reportsDir, 'global_scan.md'), mdReport ); // Rapport des dossiers legacy await this.generateLegacyDirsReport(); console.log('✅ Rapports générés dans veza-docs/_reports/'); } private generateMarkdownReport(data: any): string { let md = `# 📊 Rapport de Scan Global - Documentation Veza\n\n`; md += `**Date** : ${new Date().toLocaleDateString('fr-FR')}\n\n`; // Résumé md += `## 📈 Résumé\n\n`; md += `- **Total fichiers** : ${data.totalFiles}\n`; md += `- **Dans veza-docs** : ${data.inVezaDocs} (${Math.round(data.inVezaDocs / data.totalFiles * 100)}%)\n`; md += `- **Legacy** : ${data.legacy} (${Math.round(data.legacy / data.totalFiles * 100)}%)\n`; md += `- **Exclus** : ${data.excluded} (${Math.round(data.excluded / data.totalFiles * 100)}%)\n\n`; // Par track md += `## 🎯 Répartition par Track\n\n`; md += `| Track | Fichiers | Pourcentage |\n`; md += `|-------|----------|-------------|\n`; md += `| Current | ${data.byTrack.current} | ${Math.round(data.byTrack.current / data.totalFiles * 100)}% |\n`; md += `| Vision | ${data.byTrack.vision} | ${Math.round(data.byTrack.vision / data.totalFiles * 100)}% |\n`; md += `| Unknown | ${data.byTrack.unknown} | ${Math.round(data.byTrack.unknown / data.totalFiles * 100)}% |\n\n`; // Par domaine md += `## 🏗️ Répartition par Domaine\n\n`; md += `| Domaine | Fichiers | Pourcentage |\n`; md += `|---------|----------|-------------|\n`; Object.entries(data.byDomain).forEach(([domain, count]) => { md += `| ${domain} | ${count} | ${Math.round(count as number / data.totalFiles * 100)}% |\n`; }); md += `\n`; // Détail des fichiers md += `## 📄 Détail des Fichiers\n\n`; md += `| Chemin | Taille | Track | Domaine | Statut |\n`; md += `|--------|--------|-------|---------|--------|\n`; this.results.forEach(file => { const sizeKB = Math.round(file.size / 1024 * 100) / 100; md += `| \`${file.path}\` | ${sizeKB} KB | ${file.track} | ${file.domain} | ${file.status} |\n`; }); return md; } private async generateLegacyDirsReport(): Promise { const legacyDirs = new Set(); this.results .filter(f => f.status === 'legacy' && !f.isImage) .forEach(f => { const dir = path.dirname(f.path); if (dir !== '.' && !dir.startsWith('veza-docs/')) { legacyDirs.add(dir); } }); const legacyData = { scanDate: new Date().toISOString(), totalLegacyDirs: legacyDirs.size, legacyDirs: Array.from(legacyDirs).sort(), filesInLegacyDirs: this.results.filter(f => f.status === 'legacy').length }; const reportsDir = path.join(this.vezaDocsRoot, '_reports'); fs.writeFileSync( path.join(reportsDir, 'legacy_doc_dirs.json'), JSON.stringify(legacyData, null, 2) ); let md = `# 🗂️ Rapport des Dossiers Legacy - Documentation Veza\n\n`; md += `**Date** : ${new Date().toLocaleDateString('fr-FR')}\n\n`; md += `## 📊 Résumé\n\n`; md += `- **Dossiers legacy** : ${legacyData.totalLegacyDirs}\n`; md += `- **Fichiers dans dossiers legacy** : ${legacyData.filesInLegacyDirs}\n\n`; md += `## 📁 Dossiers à Nettoyer\n\n`; md += `| Dossier | Fichiers |\n`; md += `|---------|----------|\n`; legacyDirs.forEach(dir => { const fileCount = this.results.filter(f => f.path.startsWith(dir + '/')).length; md += `| \`${dir}\` | ${fileCount} |\n`; }); fs.writeFileSync( path.join(reportsDir, 'legacy_doc_dirs.md'), md ); } async run(): Promise { console.log('🚀 Démarrage du scan global...'); try { await this.scanAll(); await this.generateReports(); console.log('✅ Scan global terminé avec succès!'); } catch (error) { console.error('❌ Erreur lors du scan global:', error); process.exit(1); } } } // Exécution du script if (require.main === module) { const repoRoot = process.argv[2] || path.join(__dirname, '../..'); const scanner = new GlobalDocScanner(repoRoot); scanner.run(); } export { GlobalDocScanner };