327 lines
8.7 KiB
JavaScript
327 lines
8.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Script pour détecter les doublons dans les fichiers de skills
|
|
* Usage: node scripts/find-duplicates.js
|
|
*/
|
|
|
|
// Configuration
|
|
const SKILLS_DIR = path.join(__dirname, '..', 'data', 'skills');
|
|
const OUTPUT_FILE = path.join(__dirname, '..', 'duplicates-report.txt');
|
|
|
|
// Couleurs pour la console
|
|
const colors = {
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m',
|
|
reset: '\x1b[0m',
|
|
bold: '\x1b[1m'
|
|
};
|
|
|
|
function log(message, color = 'reset') {
|
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
}
|
|
|
|
function logHeader(message) {
|
|
log(`\n${colors.bold}${colors.blue}${'='.repeat(60)}${colors.reset}`);
|
|
log(`${colors.bold}${colors.blue}${message}${colors.reset}`);
|
|
log(`${colors.bold}${colors.blue}${'='.repeat(60)}${colors.reset}`);
|
|
}
|
|
|
|
function logSubHeader(message) {
|
|
log(`\n${colors.bold}${colors.cyan}${message}${colors.reset}`);
|
|
log(`${colors.cyan}${'-'.repeat(message.length)}${colors.reset}`);
|
|
}
|
|
|
|
/**
|
|
* Lit et parse un fichier JSON de skills
|
|
*/
|
|
function parseSkillsFile(filePath) {
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const data = JSON.parse(content);
|
|
return {
|
|
category: data.category,
|
|
skills: data.skills || []
|
|
};
|
|
} catch (error) {
|
|
log(`❌ Erreur lors de la lecture de ${filePath}: ${error.message}`, 'red');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trouve tous les fichiers de skills
|
|
*/
|
|
function findSkillsFiles() {
|
|
try {
|
|
const files = fs.readdirSync(SKILLS_DIR);
|
|
return files
|
|
.filter(file => file.endsWith('.json'))
|
|
.map(file => path.join(SKILLS_DIR, file));
|
|
} catch (error) {
|
|
log(`❌ Erreur lors de la lecture du répertoire ${SKILLS_DIR}: ${error.message}`, 'red');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyse tous les fichiers et collecte les informations
|
|
*/
|
|
function analyzeSkills() {
|
|
const files = findSkillsFiles();
|
|
const allSkills = [];
|
|
const categoryStats = {};
|
|
|
|
logHeader('ANALYSE DES FICHIERS DE SKILLS');
|
|
log(`📁 Répertoire: ${SKILLS_DIR}`);
|
|
log(`🔍 Fichiers trouvés: ${files.length}`);
|
|
|
|
files.forEach(filePath => {
|
|
const fileName = path.basename(filePath, '.json');
|
|
const data = parseSkillsFile(filePath);
|
|
|
|
if (data) {
|
|
const category = data.category;
|
|
const skillsCount = data.skills.length;
|
|
|
|
// Statistiques par catégorie
|
|
categoryStats[category] = {
|
|
file: fileName,
|
|
count: skillsCount,
|
|
skills: data.skills
|
|
};
|
|
|
|
// Collecte de tous les skills avec métadonnées
|
|
data.skills.forEach(skill => {
|
|
allSkills.push({
|
|
id: skill.id,
|
|
name: skill.name,
|
|
category: category,
|
|
file: fileName,
|
|
description: skill.description?.substring(0, 100) + '...' || 'Pas de description'
|
|
});
|
|
});
|
|
|
|
log(`✅ ${category}: ${skillsCount} skills (${fileName}.json)`, 'green');
|
|
}
|
|
});
|
|
|
|
return { allSkills, categoryStats };
|
|
}
|
|
|
|
/**
|
|
* Détecte les doublons par ID
|
|
*/
|
|
function findDuplicateIds(allSkills) {
|
|
const idCounts = {};
|
|
const duplicates = [];
|
|
|
|
allSkills.forEach(skill => {
|
|
if (!idCounts[skill.id]) {
|
|
idCounts[skill.id] = [];
|
|
}
|
|
idCounts[skill.id].push(skill);
|
|
});
|
|
|
|
Object.entries(idCounts).forEach(([id, skills]) => {
|
|
if (skills.length > 1) {
|
|
duplicates.push({
|
|
id,
|
|
count: skills.length,
|
|
occurrences: skills
|
|
});
|
|
}
|
|
});
|
|
|
|
return duplicates;
|
|
}
|
|
|
|
/**
|
|
* Détecte les doublons par nom (avec tolérance pour les variations)
|
|
*/
|
|
function findDuplicateNames(allSkills) {
|
|
const nameCounts = {};
|
|
const duplicates = [];
|
|
|
|
allSkills.forEach(skill => {
|
|
const normalizedName = skill.name.toLowerCase().trim();
|
|
if (!nameCounts[normalizedName]) {
|
|
nameCounts[normalizedName] = [];
|
|
}
|
|
nameCounts[normalizedName].push(skill);
|
|
});
|
|
|
|
Object.entries(nameCounts).forEach(([name, skills]) => {
|
|
if (skills.length > 1) {
|
|
duplicates.push({
|
|
name,
|
|
count: skills.length,
|
|
occurrences: skills
|
|
});
|
|
}
|
|
});
|
|
|
|
return duplicates;
|
|
}
|
|
|
|
/**
|
|
* Génère le rapport
|
|
*/
|
|
function generateReport(allSkills, categoryStats, duplicateIds, duplicateNames) {
|
|
const report = [];
|
|
|
|
report.push('='.repeat(80));
|
|
report.push('RAPPORT DE DÉTECTION DE DOUBLONS - SKILLS');
|
|
report.push('='.repeat(80));
|
|
report.push(`Généré le: ${new Date().toLocaleString('fr-FR')}`);
|
|
report.push('');
|
|
|
|
// Statistiques générales
|
|
report.push('📊 STATISTIQUES GÉNÉRALES');
|
|
report.push('-'.repeat(30));
|
|
report.push(`Total de skills: ${allSkills.length}`);
|
|
report.push(`Total de catégories: ${Object.keys(categoryStats).length}`);
|
|
report.push('');
|
|
|
|
// Statistiques par catégorie
|
|
report.push('📁 STATISTIQUES PAR CATÉGORIE');
|
|
report.push('-'.repeat(30));
|
|
Object.entries(categoryStats).forEach(([category, stats]) => {
|
|
report.push(`${category}: ${stats.count} skills (${stats.file}.json)`);
|
|
});
|
|
report.push('');
|
|
|
|
// Doublons par ID
|
|
if (duplicateIds.length > 0) {
|
|
report.push('🚨 DOUBLONS PAR ID');
|
|
report.push('-'.repeat(30));
|
|
duplicateIds.forEach(duplicate => {
|
|
report.push(`ID: "${duplicate.id}" (${duplicate.count} occurrences)`);
|
|
duplicate.occurrences.forEach(occurrence => {
|
|
report.push(` - ${occurrence.category} (${occurrence.file}.json): ${occurrence.name}`);
|
|
});
|
|
report.push('');
|
|
});
|
|
} else {
|
|
report.push('✅ AUCUN DOUBLON PAR ID DÉTECTÉ');
|
|
report.push('');
|
|
}
|
|
|
|
// Doublons par nom
|
|
if (duplicateNames.length > 0) {
|
|
report.push('⚠️ DOUBLONS PAR NOM (POTENTIELS)');
|
|
report.push('-'.repeat(40));
|
|
duplicateNames.forEach(duplicate => {
|
|
report.push(`Nom: "${duplicate.name}" (${duplicate.count} occurrences)`);
|
|
duplicate.occurrences.forEach(occurrence => {
|
|
report.push(` - ${occurrence.category} (${occurrence.file}.json): ID="${occurrence.id}"`);
|
|
});
|
|
report.push('');
|
|
});
|
|
} else {
|
|
report.push('✅ AUCUN DOUBLON PAR NOM DÉTECTÉ');
|
|
report.push('');
|
|
}
|
|
|
|
// Recommandations
|
|
report.push('💡 RECOMMANDATIONS');
|
|
report.push('-'.repeat(20));
|
|
if (duplicateIds.length > 0) {
|
|
report.push('• Supprimer les doublons par ID (priorité haute)');
|
|
report.push('• Garder la version dans la catégorie la plus appropriée');
|
|
}
|
|
if (duplicateNames.length > 0) {
|
|
report.push('• Vérifier les doublons par nom (peuvent être légitimes)');
|
|
report.push('• S\'assurer que les IDs sont uniques');
|
|
}
|
|
if (duplicateIds.length === 0 && duplicateNames.length === 0) {
|
|
report.push('• Aucune action requise - tous les skills sont uniques !');
|
|
}
|
|
|
|
return report.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Affiche le rapport dans la console
|
|
*/
|
|
function displayReport(report) {
|
|
logHeader('RAPPORT COMPLET');
|
|
console.log(report);
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde le rapport dans un fichier
|
|
*/
|
|
function saveReport(report) {
|
|
try {
|
|
fs.writeFileSync(OUTPUT_FILE, report, 'utf8');
|
|
log(`\n💾 Rapport sauvegardé dans: ${OUTPUT_FILE}`, 'green');
|
|
} catch (error) {
|
|
log(`❌ Erreur lors de la sauvegarde du rapport: ${error.message}`, 'red');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fonction principale
|
|
*/
|
|
function main() {
|
|
try {
|
|
logHeader('DÉTECTION DE DOUBLONS DANS LES SKILLS');
|
|
|
|
// Analyse des fichiers
|
|
const { allSkills, categoryStats } = analyzeSkills();
|
|
|
|
if (allSkills.length === 0) {
|
|
log('❌ Aucun skill trouvé. Vérifiez le répertoire.', 'red');
|
|
return;
|
|
}
|
|
|
|
// Détection des doublons
|
|
logSubHeader('DÉTECTION DES DOUBLONS');
|
|
const duplicateIds = findDuplicateIds(allSkills);
|
|
const duplicateNames = findDuplicateNames(allSkills);
|
|
|
|
log(`🔍 Doublons par ID: ${duplicateIds.length}`, duplicateIds.length > 0 ? 'red' : 'green');
|
|
log(`🔍 Doublons par nom: ${duplicateNames.length}`, duplicateNames.length > 0 ? 'yellow' : 'green');
|
|
|
|
// Génération et affichage du rapport
|
|
const report = generateReport(allSkills, categoryStats, duplicateIds, duplicateNames);
|
|
displayReport(report);
|
|
|
|
// Sauvegarde du rapport
|
|
saveReport(report);
|
|
|
|
// Résumé final
|
|
logHeader('RÉSUMÉ');
|
|
if (duplicateIds.length === 0 && duplicateNames.length === 0) {
|
|
log('🎉 Aucun doublon détecté ! Tous les skills sont uniques.', 'green');
|
|
} else {
|
|
log(`⚠️ ${duplicateIds.length} doublons par ID et ${duplicateNames.length} doublons par nom détectés.`, 'yellow');
|
|
log('Consultez le rapport ci-dessus pour plus de détails.', 'yellow');
|
|
}
|
|
|
|
} catch (error) {
|
|
log(`❌ Erreur fatale: ${error.message}`, 'red');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Exécution du script
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = {
|
|
analyzeSkills,
|
|
findDuplicateIds,
|
|
findDuplicateNames,
|
|
generateReport
|
|
};
|