331 lines
10 KiB
TypeScript
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 };
|