veza/tools/sync_docs.ts
2025-12-03 22:56:50 +01:00

415 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env tsx
/**
* Script de synchronisation de la documentation Docusaurus
* Vérifie la cohérence entre Vision et Current
* Génère des rapports de synchronisation
*/
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, relative } from 'path';
import { execSync } from 'child_process';
interface DocFile {
path: string;
content: string;
lastModified: Date;
size: number;
}
interface SyncReport {
timestamp: Date;
visionFiles: DocFile[];
currentFiles: DocFile[];
inconsistencies: Inconsistency[];
recommendations: string[];
}
interface Inconsistency {
type: 'missing' | 'outdated' | 'mismatch' | 'broken-link';
file: string;
description: string;
severity: 'low' | 'medium' | 'high' | 'critical';
suggestion: string;
}
class DocSyncManager {
private visionPath = 'veza-docs/docs-vision';
private currentPath = 'veza-docs/docs-current';
private assetsPath = 'veza-docs/docs-assets';
private reportPath = 'REPORTS';
constructor() {
console.log('🔄 Initialisation du gestionnaire de synchronisation de documentation');
}
/**
* Scanne un répertoire et retourne la liste des fichiers
*/
private scanDirectory(dirPath: string): DocFile[] {
if (!existsSync(dirPath)) {
console.warn(`⚠️ Répertoire non trouvé: ${dirPath}`);
return [];
}
const files: DocFile[] = [];
const scanDir = (currentPath: string) => {
const entries = readdirSync(currentPath);
for (const entry of entries) {
const fullPath = join(currentPath, entry);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (entry.endsWith('.md')) {
try {
const content = readFileSync(fullPath, 'utf-8');
files.push({
path: relative(dirPath, fullPath),
content,
lastModified: stat.mtime,
size: stat.size
});
} catch (error) {
console.error(`❌ Erreur lecture fichier ${fullPath}:`, error);
}
}
}
};
scanDir(dirPath);
return files;
}
/**
* Vérifie la cohérence entre Vision et Current
*/
private checkConsistency(visionFiles: DocFile[], currentFiles: DocFile[]): Inconsistency[] {
const inconsistencies: Inconsistency[] = [];
// Vérifier les fichiers manquants
const visionPaths = new Set(visionFiles.map(f => f.path));
const currentPaths = new Set(currentFiles.map(f => f.path));
// Fichiers manquants dans Current
for (const visionFile of visionFiles) {
if (!currentPaths.has(visionFile.path)) {
inconsistencies.push({
type: 'missing',
file: visionFile.path,
description: `Fichier Vision manquant dans Current: ${visionFile.path}`,
severity: 'medium',
suggestion: `Créer le fichier correspondant dans docs-current/`
});
}
}
// Vérifier les liens brisés
for (const file of [...visionFiles, ...currentFiles]) {
const brokenLinks = this.findBrokenLinks(file.content, file.path);
inconsistencies.push(...brokenLinks);
}
// Vérifier les badges de statut
for (const file of [...visionFiles, ...currentFiles]) {
const badgeIssues = this.checkStatusBadges(file);
inconsistencies.push(...badgeIssues);
}
return inconsistencies;
}
/**
* Trouve les liens brisés dans un fichier
*/
private findBrokenLinks(content: string, filePath: string): Inconsistency[] {
const inconsistencies: Inconsistency[] = [];
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match;
while ((match = linkRegex.exec(content)) !== null) {
const [, text, link] = match;
// Vérifier les liens internes
if (link.startsWith('./') || link.startsWith('../')) {
const resolvedPath = this.resolveLink(link, filePath);
if (!existsSync(resolvedPath)) {
inconsistencies.push({
type: 'broken-link',
file: filePath,
description: `Lien brisé: ${link} (vers ${text})`,
severity: 'medium',
suggestion: `Vérifier le chemin: ${resolvedPath}`
});
}
}
}
return inconsistencies;
}
/**
* Résout un lien relatif
*/
private resolveLink(link: string, fromFile: string): string {
const baseDir = fromFile.includes('docs-vision') ? this.visionPath : this.currentPath;
const fromDir = join(baseDir, fromFile.split('/').slice(0, -1).join('/'));
return join(fromDir, link);
}
/**
* Vérifie les badges de statut
*/
private checkStatusBadges(file: DocFile): Inconsistency[] {
const inconsistencies: Inconsistency[] = [];
const content = file.content;
// Vérifier la présence de badges
if (!content.includes(':::note') && !content.includes(':::tip') && !content.includes(':::warning')) {
inconsistencies.push({
type: 'mismatch',
file: file.path,
description: 'Fichier sans badge de statut',
severity: 'low',
suggestion: 'Ajouter un badge de statut (NOTE, TIP, WARNING)'
});
}
// Vérifier la cohérence des badges
if (file.path.includes('docs-vision') && !content.includes('CIBLE')) {
inconsistencies.push({
type: 'mismatch',
file: file.path,
description: 'Fichier Vision sans badge CIBLE',
severity: 'medium',
suggestion: 'Ajouter le badge CIBLE pour les fichiers Vision'
});
}
if (file.path.includes('docs-current') && !content.includes('ÉTAT ACTUEL')) {
inconsistencies.push({
type: 'mismatch',
file: file.path,
description: 'Fichier Current sans badge ÉTAT ACTUEL',
severity: 'medium',
suggestion: 'Ajouter le badge ÉTAT ACTUEL pour les fichiers Current'
});
}
return inconsistencies;
}
/**
* Génère des recommandations
*/
private generateRecommendations(inconsistencies: Inconsistency[]): string[] {
const recommendations: string[] = [];
const criticalIssues = inconsistencies.filter(i => i.severity === 'critical');
const highIssues = inconsistencies.filter(i => i.severity === 'high');
const mediumIssues = inconsistencies.filter(i => i.severity === 'medium');
if (criticalIssues.length > 0) {
recommendations.push(`🚨 ${criticalIssues.length} problème(s) critique(s) à résoudre immédiatement`);
}
if (highIssues.length > 0) {
recommendations.push(`⚠️ ${highIssues.length} problème(s) de haute priorité à traiter cette semaine`);
}
if (mediumIssues.length > 0) {
recommendations.push(`📝 ${mediumIssues.length} problème(s) de priorité moyenne à traiter prochainement`);
}
// Recommandations spécifiques
const missingFiles = inconsistencies.filter(i => i.type === 'missing');
if (missingFiles.length > 0) {
recommendations.push(`📄 ${missingFiles.length} fichier(s) manquant(s) dans Current - synchroniser avec Vision`);
}
const brokenLinks = inconsistencies.filter(i => i.type === 'broken-link');
if (brokenLinks.length > 0) {
recommendations.push(`🔗 ${brokenLinks.length} lien(s) brisé(s) à corriger`);
}
const badgeIssues = inconsistencies.filter(i => i.type === 'mismatch');
if (badgeIssues.length > 0) {
recommendations.push(`🏷️ ${badgeIssues.length} problème(s) de badges à corriger`);
}
return recommendations;
}
/**
* Génère un rapport de synchronisation
*/
private generateReport(visionFiles: DocFile[], currentFiles: DocFile[], inconsistencies: Inconsistency[]): SyncReport {
return {
timestamp: new Date(),
visionFiles,
currentFiles,
inconsistencies,
recommendations: this.generateRecommendations(inconsistencies)
};
}
/**
* Sauvegarde le rapport
*/
private saveReport(report: SyncReport): void {
const timestamp = report.timestamp.toISOString().split('T')[0];
const reportFile = join(this.reportPath, `doc-sync-${timestamp}.md`);
const reportContent = this.formatReport(report);
writeFileSync(reportFile, reportContent, 'utf-8');
console.log(`📊 Rapport sauvegardé: ${reportFile}`);
}
/**
* Formate le rapport en Markdown
*/
private formatReport(report: SyncReport): string {
const { timestamp, visionFiles, currentFiles, inconsistencies, recommendations } = report;
let content = `# 📚 Rapport de Synchronisation Documentation\n\n`;
content += `**Date** : ${timestamp.toLocaleDateString('fr-FR')}\n`;
content += `**Heure** : ${timestamp.toLocaleTimeString('fr-FR')}\n\n`;
// Statistiques
content += `## 📊 Statistiques\n\n`;
content += `- **Fichiers Vision** : ${visionFiles.length}\n`;
content += `- **Fichiers Current** : ${currentFiles.length}\n`;
content += `- **Incohérences** : ${inconsistencies.length}\n`;
content += `- **Recommandations** : ${recommendations.length}\n\n`;
// Recommandations
if (recommendations.length > 0) {
content += `## 🎯 Recommandations\n\n`;
recommendations.forEach(rec => {
content += `- ${rec}\n`;
});
content += `\n`;
}
// Incohérences par sévérité
const critical = inconsistencies.filter(i => i.severity === 'critical');
const high = inconsistencies.filter(i => i.severity === 'high');
const medium = inconsistencies.filter(i => i.severity === 'medium');
const low = inconsistencies.filter(i => i.severity === 'low');
if (critical.length > 0) {
content += `## 🚨 Problèmes Critiques\n\n`;
critical.forEach(issue => {
content += `### ${issue.file}\n`;
content += `- **Type** : ${issue.type}\n`;
content += `- **Description** : ${issue.description}\n`;
content += `- **Suggestion** : ${issue.suggestion}\n\n`;
});
}
if (high.length > 0) {
content += `## ⚠️ Problèmes de Haute Priorité\n\n`;
high.forEach(issue => {
content += `### ${issue.file}\n`;
content += `- **Type** : ${issue.type}\n`;
content += `- **Description** : ${issue.description}\n`;
content += `- **Suggestion** : ${issue.suggestion}\n\n`;
});
}
if (medium.length > 0) {
content += `## 📝 Problèmes de Priorité Moyenne\n\n`;
medium.forEach(issue => {
content += `### ${issue.file}\n`;
content += `- **Type** : ${issue.type}\n`;
content += `- **Description** : ${issue.description}\n`;
content += `- **Suggestion** : ${issue.suggestion}\n\n`;
});
}
if (low.length > 0) {
content += `## Problèmes de Priorité Faible\n\n`;
low.forEach(issue => {
content += `### ${issue.file}\n`;
content += `- **Type** : ${issue.type}\n`;
content += `- **Description** : ${issue.description}\n`;
content += `- **Suggestion** : ${issue.suggestion}\n\n`;
});
}
// Fichiers récents
content += `## 📄 Fichiers Récents\n\n`;
const recentFiles = [...visionFiles, ...currentFiles]
.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime())
.slice(0, 10);
recentFiles.forEach(file => {
const type = file.path.includes('docs-vision') ? 'Vision' : 'Current';
content += `- **${type}** : ${file.path} (${file.lastModified.toLocaleDateString('fr-FR')})\n`;
});
content += `\n---\n\n`;
content += `*Rapport généré automatiquement par DocSyncManager*\n`;
return content;
}
/**
* Exécute la synchronisation complète
*/
public async sync(): Promise<void> {
console.log('🔄 Démarrage de la synchronisation de documentation...');
try {
// Scanner les répertoires
console.log('📁 Scan des fichiers Vision...');
const visionFiles = this.scanDirectory(this.visionPath);
console.log('📁 Scan des fichiers Current...');
const currentFiles = this.scanDirectory(this.currentPath);
// Vérifier la cohérence
console.log('🔍 Vérification de la cohérence...');
const inconsistencies = this.checkConsistency(visionFiles, currentFiles);
// Générer le rapport
console.log('📊 Génération du rapport...');
const report = this.generateReport(visionFiles, currentFiles, inconsistencies);
// Sauvegarder le rapport
this.saveReport(report);
// Afficher le résumé
console.log('\n📋 Résumé de la synchronisation:');
console.log(` - Fichiers Vision: ${visionFiles.length}`);
console.log(` - Fichiers Current: ${currentFiles.length}`);
console.log(` - Incohérences: ${inconsistencies.length}`);
console.log(` - Recommandations: ${report.recommendations.length}`);
if (inconsistencies.length > 0) {
console.log('\n⚠ Incohérences détectées:');
inconsistencies.forEach(issue => {
const emoji = issue.severity === 'critical' ? '🚨' :
issue.severity === 'high' ? '⚠️' :
issue.severity === 'medium' ? '📝' : '';
console.log(` ${emoji} ${issue.file}: ${issue.description}`);
});
} else {
console.log('\n✅ Aucune incohérence détectée!');
}
} catch (error) {
console.error('❌ Erreur lors de la synchronisation:', error);
process.exit(1);
}
}
}
// Exécution du script
if (require.main === module) {
const syncManager = new DocSyncManager();
syncManager.sync().catch(console.error);
}
export default DocSyncManager;