veza/veza-docs/scripts/global_scan.ts

331 lines
10 KiB
TypeScript

#!/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<void> {
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<void> {
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<void> {
const legacyDirs = new Set<string>();
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<void> {
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 };