devsite/strapi_extraction/analyse-site-architecture.js
2026-03-15 15:28:26 +01:00

418 lines
15 KiB
JavaScript

const fs = require('fs');
const path = require('path');
// Configuration
const SITE_ROOT = '../'; // Depuis strapi_extraction vers la racine
const DOCS_DIR = './docs';
const OUTPUT_FILE = '99-site-architecture.md';
// Fonction pour lire récursivement les fichiers
function readDirectoryRecursive(dir, extensions = [], maxDepth = 3, currentDepth = 0) {
const files = [];
if (currentDepth >= maxDepth) return files;
try {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
// Ignorer certains dossiers
if (['node_modules', '.next', '.git', 'raw-data', 'clean-data', 'docs'].includes(item)) {
continue;
}
files.push(...readDirectoryRecursive(fullPath, extensions, maxDepth, currentDepth + 1));
} else if (stat.isFile()) {
const ext = path.extname(item);
if (extensions.length === 0 || extensions.includes(ext)) {
files.push({
path: fullPath,
relativePath: path.relative(SITE_ROOT, fullPath),
name: item,
extension: ext,
size: stat.size
});
}
}
}
} catch (error) {
console.warn(`Impossible de lire le dossier ${dir}:`, error.message);
}
return files;
}
// Analyser les pages Next.js
function analyzePages() {
const pagesDir = path.join(SITE_ROOT, 'app');
const pages = [];
if (!fs.existsSync(pagesDir)) {
return pages;
}
// Rechercher les fichiers page.tsx/page.js
const pageFiles = readDirectoryRecursive(pagesDir, ['.tsx', '.ts', '.jsx', '.js'], 3)
.filter(file => file.name.startsWith('page.'));
pageFiles.forEach(file => {
try {
const content = fs.readFileSync(file.path, 'utf8');
const dirName = path.dirname(file.relativePath).replace('app', '').replace(/\\/g, '/');
const route = dirName === '' ? '/' : dirName;
// Extraire des informations du contenu
const hasGetData = content.includes('getHomepageData') || content.includes('getProjectsData') || content.includes('getCompetencesData');
const hasForm = content.includes('form') || content.includes('Form');
const hasCarousel = content.includes('carousel') || content.includes('Carousel');
const hasMarkdown = content.includes('ReactMarkdown') || content.includes('markdown');
pages.push({
route,
file: file.relativePath,
hasGetData,
hasForm,
hasCarousel,
hasMarkdown,
size: file.size
});
} catch (error) {
console.warn(`Erreur lors de l'analyse de ${file.path}:`, error.message);
}
});
return pages.sort((a, b) => a.route.localeCompare(b.route));
}
// Analyser les composants
function analyzeComponents() {
const componentsDir = path.join(SITE_ROOT, 'app/components');
const components = [];
if (!fs.existsSync(componentsDir)) {
return components;
}
const componentFiles = readDirectoryRecursive(componentsDir, ['.tsx', '.ts', '.jsx', '.js'], 2);
componentFiles.forEach(file => {
try {
const content = fs.readFileSync(file.path, 'utf8');
const componentName = path.basename(file.name, path.extname(file.name));
// Analyser le type de composant
const isLayout = content.includes('children') && (content.includes('header') || content.includes('nav'));
const isForm = content.includes('form') || content.includes('input') || content.includes('submit');
const isCarousel = content.includes('carousel') || content.includes('slide') || content.includes('swiper');
const isCard = content.includes('card') || componentName.toLowerCase().includes('card');
const usesStrapi = content.includes('apiUrl') || content.includes('populate');
components.push({
name: componentName,
file: file.relativePath,
isLayout,
isForm,
isCarousel,
isCard,
usesStrapi,
size: file.size
});
} catch (error) {
console.warn(`Erreur lors de l'analyse de ${file.path}:`, error.message);
}
});
return components.sort((a, b) => a.name.localeCompare(b.name));
}
// Analyser les utilitaires
function analyzeUtils() {
const utilsDir = path.join(SITE_ROOT, 'app/utils');
const utils = [];
if (!fs.existsSync(utilsDir)) {
return utils;
}
const utilFiles = readDirectoryRecursive(utilsDir, ['.tsx', '.ts', '.jsx', '.js'], 2);
utilFiles.forEach(file => {
try {
const content = fs.readFileSync(file.path, 'utf8');
const utilName = path.basename(file.name, path.extname(file.name));
// Analyser le type d'utilitaire
const isApiUtil = content.includes('fetch') || content.includes('api');
const isDataUtil = content.includes('populate') || content.includes('strapi');
const isHelperUtil = content.includes('export function') || content.includes('export const');
utils.push({
name: utilName,
file: file.relativePath,
isApiUtil,
isDataUtil,
isHelperUtil,
size: file.size
});
} catch (error) {
console.warn(`Erreur lors de l'analyse de ${file.path}:`, error.message);
}
});
return utils.sort((a, b) => a.name.localeCompare(b.name));
}
// Analyser la configuration
function analyzeConfig() {
const configs = [];
// Fichiers de configuration à analyser
const configFiles = [
'package.json',
'next.config.js',
'next.config.mjs',
'tailwind.config.js',
'tailwind.config.ts',
'tsconfig.json',
'web.config'
];
configFiles.forEach(configFile => {
const configPath = path.join(SITE_ROOT, configFile);
if (fs.existsSync(configPath)) {
try {
const content = fs.readFileSync(configPath, 'utf8');
let description = '';
switch (configFile) {
case 'package.json':
const pkg = JSON.parse(content);
description = `Dependencies: ${Object.keys(pkg.dependencies || {}).length}, DevDependencies: ${Object.keys(pkg.devDependencies || {}).length}`;
break;
case 'web.config':
description = 'Configuration IIS pour Windows Server';
break;
case 'next.config.js':
case 'next.config.mjs':
description = 'Configuration Next.js';
break;
case 'tailwind.config.js':
case 'tailwind.config.ts':
description = 'Configuration Tailwind CSS';
break;
case 'tsconfig.json':
description = 'Configuration TypeScript';
break;
}
configs.push({
file: configFile,
description,
size: fs.statSync(configPath).size
});
} catch (error) {
console.warn(`Erreur lors de l'analyse de ${configFile}:`, error.message);
}
}
});
return configs;
}
// Générer la documentation d'architecture
function generateArchitectureDoc() {
console.log('🏗️ Analyse de l\'architecture du site...\n');
const pages = analyzePages();
const components = analyzeComponents();
const utils = analyzeUtils();
const configs = analyzeConfig();
console.log(`📄 ${pages.length} pages trouvées`);
console.log(`🧩 ${components.length} composants trouvés`);
console.log(`🔧 ${utils.length} utilitaires trouvés`);
console.log(`⚙️ ${configs.length} fichiers de configuration trouvés`);
// Générer le contenu Markdown
let content = `# Architecture et Navigation - Site Fernand Gras-Calvet\n\n`;
content += `*Documentation générée automatiquement le ${new Date().toLocaleString('fr-FR')}*\n\n`;
content += `---\n\n`;
// Vue d'ensemble
content += `## 🏠 Vue d'ensemble du site\n\n`;
content += `**Site personnel et portfolio** de Fernand Gras-Calvet, étudiant à l'École 42 Perpignan.\n\n`;
content += `### Technologies principales\n`;
content += `- **Frontend :** Next.js, React, TypeScript, Tailwind CSS\n`;
content += `- **Backend :** Strapi (Headless CMS)\n`;
content += `- **Hébergement :** Windows Server 2025 + IIS\n`;
content += `- **Base de données :** PostgreSQL/MySQL\n\n`;
// Structure des pages
content += `## 📄 Structure des pages (${pages.length} pages)\n\n`;
if (pages.length > 0) {
content += `| Route | Fichier | Fonctionnalités |\n`;
content += `|-------|---------|----------------|\n`;
pages.forEach(page => {
const features = [];
if (page.hasGetData) features.push('📊 Données Strapi');
if (page.hasForm) features.push('📝 Formulaire');
if (page.hasCarousel) features.push('🎠 Carousel');
if (page.hasMarkdown) features.push('📝 Markdown');
content += `| \`${page.route}\` | ${page.file} | ${features.join(', ') || 'Page statique'} |\n`;
});
} else {
content += `*Aucune page trouvée dans le dossier app/*\n`;
}
content += `\n`;
// Navigation et UX
content += `## 🧭 Navigation et expérience utilisateur\n\n`;
content += `### Sections principales\n`;
content += `1. **Accueil** (\`/\`) - Présentation personnelle et CV\n`;
content += `2. **Projets** - Portfolio des projets École 42\n`;
content += `3. **Compétences** - Domaines d'expertise (IA, Web, 3D, Domotique)\n`;
content += `4. **Contact** - Formulaire de contact\n\n`;
content += `### Fonctionnalités interactives\n`;
const hasCarousel = pages.some(p => p.hasCarousel) || components.some(c => c.isCarousel);
const hasForm = pages.some(p => p.hasForm) || components.some(c => c.isForm);
if (hasCarousel) content += `- 🎠 **Carousel d'images** pour présenter les projets\n`;
if (hasForm) content += `- 📝 **Formulaire de contact** interactif\n`;
content += `- 🔍 **Glossaire interactif** avec détection automatique de mots-clés\n`;
content += `- 📱 **Design responsive** adaptatif\n`;
content += `- ⚡ **Chargement rapide** grâce à Next.js\n\n`;
// Composants
if (components.length > 0) {
content += `## 🧩 Composants React (${components.length} composants)\n\n`;
const layoutComponents = components.filter(c => c.isLayout);
const formComponents = components.filter(c => c.isForm);
const carouselComponents = components.filter(c => c.isCarousel);
const cardComponents = components.filter(c => c.isCard);
const strapiComponents = components.filter(c => c.usesStrapi);
if (layoutComponents.length > 0) {
content += `### 🏗️ Composants de mise en page\n`;
layoutComponents.forEach(comp => {
content += `- **${comp.name}** - Layout principal avec navigation\n`;
});
content += `\n`;
}
if (strapiComponents.length > 0) {
content += `### 📊 Composants connectés à Strapi\n`;
strapiComponents.forEach(comp => {
content += `- **${comp.name}** - Récupère et affiche des données du CMS\n`;
});
content += `\n`;
}
if (formComponents.length > 0) {
content += `### 📝 Composants de formulaire\n`;
formComponents.forEach(comp => {
content += `- **${comp.name}** - Gestion des saisies utilisateur\n`;
});
content += `\n`;
}
if (carouselComponents.length > 0) {
content += `### 🎠 Composants de carousel\n`;
carouselComponents.forEach(comp => {
content += `- **${comp.name}** - Affichage rotatif d'images\n`;
});
content += `\n`;
}
}
// Utilitaires
if (utils.length > 0) {
content += `## 🔧 Utilitaires et helpers (${utils.length} fichiers)\n\n`;
const apiUtils = utils.filter(u => u.isApiUtil);
const dataUtils = utils.filter(u => u.isDataUtil);
if (apiUtils.length > 0) {
content += `### 🌐 Utilitaires API\n`;
apiUtils.forEach(util => {
content += `- **${util.name}** - Gestion des appels API\n`;
});
content += `\n`;
}
if (dataUtils.length > 0) {
content += `### 📊 Utilitaires de données\n`;
dataUtils.forEach(util => {
content += `- **${util.name}** - Traitement des données Strapi\n`;
});
content += `\n`;
}
}
// Flux de données
content += `## 🔄 Flux de données\n\n`;
content += `### Architecture Headless CMS\n`;
content += `1. **Strapi** (Backend) stocke le contenu\n`;
content += `2. **API REST** expose les données (\`/api/projects\`, \`/api/competences\`, etc.)\n`;
content += `3. **Next.js** (Frontend) récupère et affiche les données\n`;
content += `4. **Génération statique** pour les performances\n\n`;
content += `### Endpoints Strapi utilisés\n`;
content += `- \`/api/homepages?populate=*\` - Contenu de la page d'accueil\n`;
content += `- \`/api/projects?populate=*\` - Liste des projets (17 projets)\n`;
content += `- \`/api/competences?populate=*\` - Compétences (4 domaines)\n`;
content += `- \`/api/messages\` - Messages du formulaire de contact\n\n`;
// Configuration
if (configs.length > 0) {
content += `## ⚙️ Configuration et déploiement\n\n`;
configs.forEach(config => {
content += `- **${config.file}** - ${config.description}\n`;
});
content += `\n`;
}
// Guide pour le chatbot
content += `## 🤖 Guide pour l'assistant IA\n\n`;
content += `### Comment guider les visiteurs\n`;
content += `- **Page d'accueil** : Présentation générale, CV, parcours\n`;
content += `- **Section Projets** : Portfolio technique, projets École 42\n`;
content += `- **Section Compétences** : Expertise IA, développement web, impression 3D, domotique\n`;
content += `- **Formulaire de contact** : Pour prendre contact directement\n\n`;
content += `### Phrases d'orientation suggérées\n`;
content += `- *"Vous pouvez consulter mes projets dans la section dédiée"*\n`;
content += `- *"Ma présentation complète se trouve sur la page d'accueil"*\n`;
content += `- *"Pour en savoir plus sur mes compétences en [domaine], consultez la section Compétences"*\n`;
content += `- *"N'hésitez pas à me contacter via le formulaire de contact"*\n\n`;
// Métadonnées techniques
content += `---\n\n`;
content += `*Analyse générée automatiquement - ${pages.length} pages, ${components.length} composants, ${utils.length} utilitaires analysés*`;
// Sauvegarder le fichier
const outputPath = path.join(DOCS_DIR, OUTPUT_FILE);
fs.writeFileSync(outputPath, content, 'utf8');
console.log(`\n✅ Documentation d'architecture générée : ${OUTPUT_FILE}`);
console.log(`📁 Sauvegardée dans : ${outputPath}`);
console.log(`📊 ${content.length} caractères de documentation générés`);
return {
pages: pages.length,
components: components.length,
utils: utils.length,
configs: configs.length,
outputFile: OUTPUT_FILE,
contentLength: content.length
};
}
// Exécuter l'analyse
generateArchitectureDoc().catch(console.error);