docstodev 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ai/analyzer.ts +6 -2
- package/src/analyzers/languageAnalyzer.ts +580 -0
- package/src/cache/cacheManager.ts +179 -0
- package/src/cli/index.ts +307 -23
- package/src/commands/generateSummary.ts +129 -39
- package/src/commands/run.ts +312 -75
- package/src/exporters/html.ts +604 -106
- package/.env +0 -1
- package/dist/ai/analyzer.d.ts +0 -3
- package/dist/ai/analyzer.d.ts.map +0 -1
- package/dist/ai/analyzer.js +0 -42
- package/dist/ai/analyzer.js.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -59
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/classify.d.ts +0 -2
- package/dist/commands/classify.d.ts.map +0 -1
- package/dist/commands/classify.js +0 -37
- package/dist/commands/classify.js.map +0 -1
- package/dist/commands/generateSummary.d.ts +0 -5
- package/dist/commands/generateSummary.d.ts.map +0 -1
- package/dist/commands/generateSummary.js +0 -54
- package/dist/commands/generateSummary.js.map +0 -1
- package/dist/commands/run.d.ts +0 -2
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -142
- package/dist/commands/run.js.map +0 -1
- package/dist/exporters/html.d.ts +0 -2
- package/dist/exporters/html.d.ts.map +0 -1
- package/dist/exporters/html.js +0 -148
- package/dist/exporters/html.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/docs/docs-to-dev.md +0 -111
- package/docs/report.html +0 -90
- package/docs/report.pdf +0 -0
- package/docs/summary.md +0 -16
- package/src/commands/classify.ts +0 -40
- package/src/index.ts +0 -0
|
@@ -3,55 +3,145 @@
|
|
|
3
3
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
+
interface SummaryData {
|
|
7
|
+
totalFiles: number;
|
|
8
|
+
sourceFiles?: number;
|
|
9
|
+
totalComplexity?: number;
|
|
10
|
+
avgComplexity?: number;
|
|
11
|
+
totalDeps?: number;
|
|
12
|
+
topLanguages?: { lang: string; count: number }[];
|
|
13
|
+
criticalFiles?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Génère un résumé global du projet avec un ton humain et bilingue.
|
|
8
18
|
*/
|
|
9
|
-
export function generateSummary(
|
|
19
|
+
export function generateSummary(
|
|
20
|
+
docsDir: string,
|
|
21
|
+
data: SummaryData | number,
|
|
22
|
+
lang: "fr" | "en" = "fr"
|
|
23
|
+
) {
|
|
10
24
|
mkdirSync(docsDir, { recursive: true });
|
|
11
25
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
? `# 📊 Vue d'ensemble du Projet – DocsToDev
|
|
17
|
-
|
|
18
|
-
> 📅 **Rapport généré le :** ${dateStr}
|
|
19
|
-
|
|
20
|
-
## 🚀 État des lieux
|
|
21
|
-
Salut ! J'ai passé ton projet au peigne fin. Voici un résumé rapide de ce que j'ai trouvé :
|
|
22
|
-
- **Volume traité :** ${totalFiles} fichiers analysés avec succès.
|
|
23
|
-
- **Diagnostic :** La structure est claire et l'analyse est complète. ✅
|
|
24
|
-
|
|
25
|
-
## 🧭 Navigation Rapide
|
|
26
|
-
Pour aller plus loin, tu peux consulter les documents suivants :
|
|
27
|
-
- **[📂 Arborescence & Analyse](../docs/docs-to-dev.md)** : Pour comprendre la vocation de chaque fichier et les capacités du système.
|
|
28
|
-
- **[💻 Code Source](../)** : Pour retourner explorer la racine de ton projet.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
*DocsToDev – Ton allié pour une documentation technique vivante et intelligente.*
|
|
32
|
-
`
|
|
33
|
-
: `# 📊 Project Overview – DocsToDev
|
|
26
|
+
// Support de l'ancien format (juste un nombre) et du nouveau format (objet complet)
|
|
27
|
+
const summaryData: SummaryData = typeof data === 'number'
|
|
28
|
+
? { totalFiles: data }
|
|
29
|
+
: data;
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
31
|
+
const dateStr = new Date().toLocaleString(lang === "fr" ? "fr-FR" : "en-US", {
|
|
32
|
+
dateStyle: "full",
|
|
33
|
+
timeStyle: "short"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const t = lang === "fr" ? {
|
|
37
|
+
title: "📊 Vue d'ensemble du Projet – DocsToDev",
|
|
38
|
+
genOn: "Rapport généré le",
|
|
39
|
+
status: "🚀 État des lieux",
|
|
40
|
+
intro: "Salut ! J'ai passé ton projet au peigne fin. Voici un résumé rapide de ce que j'ai trouvé :",
|
|
41
|
+
volume: "Volume traité",
|
|
42
|
+
filesAnalyzed: "fichiers analysés avec succès",
|
|
43
|
+
sourceFiles: "fichiers sources détectés",
|
|
44
|
+
diagnosis: "Diagnostic",
|
|
45
|
+
structureClear: "La structure est claire et l'analyse est complète. ✅",
|
|
46
|
+
metrics: "📈 Métriques clés",
|
|
47
|
+
complexity: "Complexité totale",
|
|
48
|
+
avgComplexity: "Complexité moyenne",
|
|
49
|
+
dependencies: "Dépendances uniques",
|
|
50
|
+
topLangs: "🔤 Langages détectés",
|
|
51
|
+
criticalFiles: "⚠️ Fichiers critiques (haute complexité)",
|
|
52
|
+
navigation: "🧭 Navigation Rapide",
|
|
53
|
+
navDesc: "Pour aller plus loin, tu peux consulter les documents suivants :",
|
|
54
|
+
treeLink: "📂 Arborescence & Analyse",
|
|
55
|
+
treeDesc: "Pour comprendre la vocation de chaque fichier et les capacités du système.",
|
|
56
|
+
codeLink: "💻 Code Source",
|
|
57
|
+
codeDesc: "Pour retourner explorer la racine de ton projet.",
|
|
58
|
+
footer: "DocsToDev – Ton allié pour une documentation technique vivante et intelligente."
|
|
59
|
+
} : {
|
|
60
|
+
title: "📊 Project Overview – DocsToDev",
|
|
61
|
+
genOn: "Generated on",
|
|
62
|
+
status: "🚀 Project Status",
|
|
63
|
+
intro: "Hey! I've scanned your entire codebase. Here's a quick summary of the findings:",
|
|
64
|
+
volume: "Volume processed",
|
|
65
|
+
filesAnalyzed: "files successfully analyzed",
|
|
66
|
+
sourceFiles: "source files detected",
|
|
67
|
+
diagnosis: "Diagnosis",
|
|
68
|
+
structureClear: "The structure is clear and the analysis is complete. ✅",
|
|
69
|
+
metrics: "📈 Key Metrics",
|
|
70
|
+
complexity: "Total complexity",
|
|
71
|
+
avgComplexity: "Average complexity",
|
|
72
|
+
dependencies: "Unique dependencies",
|
|
73
|
+
topLangs: "🔤 Languages Detected",
|
|
74
|
+
criticalFiles: "⚠️ Critical Files (high complexity)",
|
|
75
|
+
navigation: "🧭 Quick Navigation",
|
|
76
|
+
navDesc: "To dive deeper, check out these documents:",
|
|
77
|
+
treeLink: "📂 Tree & Analysis",
|
|
78
|
+
treeDesc: "Explore file purposes and system capabilities.",
|
|
79
|
+
codeLink: "💻 Source Code",
|
|
80
|
+
codeDesc: "Head back to the project root.",
|
|
81
|
+
footer: "DocsToDev – Your partner for living and intelligent technical documentation."
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let content = `# ${t.title}\n\n`;
|
|
85
|
+
content += `> 📅 **${t.genOn} :** ${dateStr}\n\n`;
|
|
86
|
+
|
|
87
|
+
content += `## ${t.status}\n`;
|
|
88
|
+
content += `${t.intro}\n`;
|
|
89
|
+
content += `- **${t.volume} :** ${summaryData.totalFiles} ${t.filesAnalyzed}.\n`;
|
|
90
|
+
|
|
91
|
+
if (summaryData.sourceFiles) {
|
|
92
|
+
content += `- **${t.sourceFiles} :** ${summaryData.sourceFiles} ${t.sourceFiles.toLowerCase()}.\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
content += `- **${t.diagnosis} :** ${t.structureClear}\n\n`;
|
|
96
|
+
|
|
97
|
+
// Métriques additionnelles si disponibles
|
|
98
|
+
if (summaryData.totalComplexity || summaryData.avgComplexity || summaryData.totalDeps) {
|
|
99
|
+
content += `## ${t.metrics}\n`;
|
|
100
|
+
|
|
101
|
+
if (summaryData.totalComplexity) {
|
|
102
|
+
content += `- **${t.complexity} :** ${summaryData.totalComplexity}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (summaryData.avgComplexity) {
|
|
106
|
+
content += `- **${t.avgComplexity} :** ${summaryData.avgComplexity}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (summaryData.totalDeps) {
|
|
110
|
+
content += `- **${t.dependencies} :** ${summaryData.totalDeps}\n`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
content += `\n`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Langages détectés
|
|
117
|
+
if (summaryData.topLanguages && summaryData.topLanguages.length > 0) {
|
|
118
|
+
content += `## ${t.topLangs}\n`;
|
|
119
|
+
summaryData.topLanguages.forEach(({ lang, count }) => {
|
|
120
|
+
content += `- **${lang}** : ${count} ${lang === "fr" ? "fichiers" : "files"}\n`;
|
|
121
|
+
});
|
|
122
|
+
content += `\n`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fichiers critiques
|
|
126
|
+
if (summaryData.criticalFiles && summaryData.criticalFiles.length > 0) {
|
|
127
|
+
content += `## ${t.criticalFiles}\n`;
|
|
128
|
+
summaryData.criticalFiles.forEach(file => {
|
|
129
|
+
content += `- \`${file}\`\n`;
|
|
130
|
+
});
|
|
131
|
+
content += `\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
content += `## ${t.navigation}\n`;
|
|
135
|
+
content += `${t.navDesc}\n`;
|
|
136
|
+
content += `- **[${t.treeLink}](../docs/docs-to-dev.md)** : ${t.treeDesc}\n`;
|
|
137
|
+
content += `- **[${t.codeLink}](../)** : ${t.codeDesc}\n\n`;
|
|
138
|
+
|
|
139
|
+
content += `---\n`;
|
|
140
|
+
content += `*${t.footer}*\n`;
|
|
50
141
|
|
|
51
142
|
const outputPath = path.join(docsDir, "summary.md");
|
|
52
143
|
writeFileSync(outputPath, content);
|
|
53
144
|
|
|
54
|
-
// Log de succès bilingue
|
|
55
145
|
const successMsg = lang === "fr"
|
|
56
146
|
? `📑 Résumé humain généré avec succès : ${outputPath}`
|
|
57
147
|
: `📑 Human-friendly summary successfully generated: ${outputPath}`;
|
package/src/commands/run.ts
CHANGED
|
@@ -2,34 +2,56 @@
|
|
|
2
2
|
import { globby } from "globby";
|
|
3
3
|
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import puppeteer from "puppeteer";
|
|
5
|
+
import puppeteer, { Browser, Page } from "puppeteer";
|
|
6
6
|
import { exportToHTML } from "../exporters/html.js";
|
|
7
7
|
import { askAI } from "../ai/analyzer.js";
|
|
8
|
+
import { getAnalyzer, getSupportedExtensions } from "../analyzers/languageAnalyzer.js";
|
|
9
|
+
import { CacheManager } from "../cache/cacheManager.js";
|
|
8
10
|
|
|
9
|
-
type TreeNode = { [key: string]: TreeNode
|
|
11
|
+
type TreeNode = { [key: string]: TreeNode };
|
|
12
|
+
|
|
13
|
+
interface FileAnalysis {
|
|
14
|
+
path: string;
|
|
15
|
+
lines: number;
|
|
16
|
+
exports: string[];
|
|
17
|
+
// Ajout de '| undefined' pour supporter les propriétés optionnelles en mode strict
|
|
18
|
+
imports: Array<{ name: string; type: string; usage?: string | undefined }>;
|
|
19
|
+
functions: string[];
|
|
20
|
+
classes: string[];
|
|
21
|
+
types: string[];
|
|
22
|
+
complexity: number;
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
const i18n = {
|
|
12
26
|
fr: {
|
|
13
|
-
role: "
|
|
14
|
-
density: "
|
|
15
|
-
exports: "
|
|
16
|
-
imports: "
|
|
17
|
-
structure: "Architecture
|
|
18
|
-
deps: "
|
|
19
|
-
aiTitle: "
|
|
27
|
+
role: "Rôle et responsabilités",
|
|
28
|
+
density: "Complexité et maintenance",
|
|
29
|
+
exports: "Exports publics",
|
|
30
|
+
imports: "Dépendances",
|
|
31
|
+
structure: "Architecture du projet",
|
|
32
|
+
deps: "Modules externes",
|
|
33
|
+
aiTitle: "Analyse Intelligente",
|
|
20
34
|
genAt: "Généré le",
|
|
21
|
-
reportTitle: "Rapport
|
|
35
|
+
reportTitle: "Rapport Technique DocsToDev",
|
|
36
|
+
functions: "Fonctions",
|
|
37
|
+
classes: "Classes",
|
|
38
|
+
types: "Types/Interfaces",
|
|
39
|
+
complexity: "Score de complexité"
|
|
22
40
|
},
|
|
23
41
|
en: {
|
|
24
|
-
role: "
|
|
25
|
-
density: "
|
|
26
|
-
exports: "
|
|
27
|
-
imports: "
|
|
42
|
+
role: "Role and responsibilities",
|
|
43
|
+
density: "Complexity & Maintenance",
|
|
44
|
+
exports: "Public exports",
|
|
45
|
+
imports: "Dependencies",
|
|
28
46
|
structure: "Project Architecture",
|
|
29
|
-
deps: "External Modules
|
|
30
|
-
aiTitle: "
|
|
47
|
+
deps: "External Modules",
|
|
48
|
+
aiTitle: "Smart Analysis",
|
|
31
49
|
genAt: "Generated on",
|
|
32
|
-
reportTitle: "DocsToDev Technical Report"
|
|
50
|
+
reportTitle: "DocsToDev Technical Report",
|
|
51
|
+
functions: "Functions",
|
|
52
|
+
classes: "Classes",
|
|
53
|
+
types: "Types/Interfaces",
|
|
54
|
+
complexity: "Complexity score"
|
|
33
55
|
}
|
|
34
56
|
};
|
|
35
57
|
|
|
@@ -37,9 +59,11 @@ function buildTree(files: string[]): TreeNode {
|
|
|
37
59
|
const root: TreeNode = {};
|
|
38
60
|
for (const file of files) {
|
|
39
61
|
const parts = file.split(path.sep);
|
|
40
|
-
let current = root;
|
|
62
|
+
let current: TreeNode = root;
|
|
41
63
|
for (const part of parts) {
|
|
42
|
-
if (!current[part])
|
|
64
|
+
if (!current[part]) {
|
|
65
|
+
current[part] = {};
|
|
66
|
+
}
|
|
43
67
|
current = current[part] as TreeNode;
|
|
44
68
|
}
|
|
45
69
|
}
|
|
@@ -57,100 +81,313 @@ function renderTree(tree: TreeNode, prefix = "", currentPath = ""): string {
|
|
|
57
81
|
const isFile = !child || Object.keys(child).length === 0;
|
|
58
82
|
const label = isFile ? `[${key}](../${fullPath.replace(/\\/g, '/')})` : `**${key}/**`;
|
|
59
83
|
output += `${prefix}${connector}${label}\n`;
|
|
60
|
-
if (child && Object.keys(child).length > 0) {
|
|
84
|
+
if (child && typeof child === 'object' && Object.keys(child).length > 0) {
|
|
61
85
|
output += renderTree(child, prefix + (isLast ? " " : "│ "), fullPath);
|
|
62
86
|
}
|
|
63
87
|
});
|
|
64
88
|
return output;
|
|
65
89
|
}
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
91
|
+
function analyzeFile(file: string, content: string): FileAnalysis {
|
|
92
|
+
const lines = content.split(/\r?\n/);
|
|
93
|
+
const analyzer = getAnalyzer(file);
|
|
94
|
+
|
|
95
|
+
if (analyzer) {
|
|
96
|
+
const result = analyzer.analyzeFile(content);
|
|
97
|
+
|
|
98
|
+
const ifCount = (content.match(/\b(if|elif|else if)\s*\(/g) || []).length;
|
|
99
|
+
const forCount = (content.match(/\b(for|foreach|while)\s*\(/g) || []).length;
|
|
100
|
+
const whileCount = (content.match(/\bwhile\s*\(/g) || []).length;
|
|
101
|
+
|
|
102
|
+
const complexity =
|
|
103
|
+
lines.length * 0.1 +
|
|
104
|
+
result.functions.length * 2 +
|
|
105
|
+
result.classes.length * 3 +
|
|
106
|
+
result.imports.length * 1.5 +
|
|
107
|
+
ifCount * 1 +
|
|
108
|
+
forCount * 1.5 +
|
|
109
|
+
whileCount * 1.5;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
path: file,
|
|
113
|
+
lines: lines.length,
|
|
114
|
+
exports: result.exports,
|
|
115
|
+
imports: result.imports,
|
|
116
|
+
functions: result.functions,
|
|
117
|
+
classes: result.classes,
|
|
118
|
+
types: result.types,
|
|
119
|
+
complexity: Math.round(complexity)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
path: file,
|
|
125
|
+
lines: lines.length,
|
|
126
|
+
exports: [],
|
|
127
|
+
imports: [],
|
|
128
|
+
functions: [],
|
|
129
|
+
classes: [],
|
|
130
|
+
types: [],
|
|
131
|
+
complexity: 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getComplexityLevel(score: number): string {
|
|
136
|
+
if (score < 50) return "🟢 Faible";
|
|
137
|
+
if (score < 150) return "🟡 Modérée";
|
|
138
|
+
if (score < 300) return "🟠 Élevée";
|
|
139
|
+
return "🔴 Très élevée";
|
|
140
|
+
}
|
|
70
141
|
|
|
142
|
+
function formatAISummary(summary: string): string {
|
|
143
|
+
let formatted = summary.replace(/([a-zA-Z0-9_-]+\.(ts|js|py|java|cs|go|rs|tsx|jsx|mjs|cjs))/g, '`$1`');
|
|
144
|
+
formatted = formatted.replace(/([a-zA-Z0-9_-]+\/[a-zA-Z0-9_\/-]+)/g, '`$1`');
|
|
145
|
+
formatted = formatted.replace(/\b([a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*|[A-Z][a-z][a-zA-Z0-9]*)\b/g, '`$1`');
|
|
146
|
+
formatted = formatted.replace(/\.\s+/g, '.\n\n');
|
|
147
|
+
formatted = formatted.replace(/`+/g, '`');
|
|
148
|
+
return formatted.trim();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function runCommand(
|
|
152
|
+
language: "fr" | "en" = "fr",
|
|
153
|
+
options: { incremental?: boolean; clearCache?: boolean } = {}
|
|
154
|
+
): Promise<number> {
|
|
155
|
+
const t = i18n[language];
|
|
156
|
+
|
|
157
|
+
console.log("\n╔══════════════════════════════════════════╗");
|
|
158
|
+
console.log("║ 🚀 DocsToDev - Analyse Avancée ║");
|
|
159
|
+
console.log("╚══════════════════════════════════════════╝\n");
|
|
160
|
+
|
|
161
|
+
const projectRoot = process.cwd();
|
|
162
|
+
const cache = new CacheManager(projectRoot);
|
|
163
|
+
|
|
164
|
+
if (options.clearCache) {
|
|
165
|
+
console.log("🗑️ Nettoyage du cache...");
|
|
166
|
+
cache.invalidateAll();
|
|
167
|
+
console.log(" ✓ Cache vidé\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log("📂 Étape 1/6 : Scan des fichiers du projet...");
|
|
171
|
+
const supportedExts = getSupportedExtensions();
|
|
71
172
|
const files = await globby(["**/*"], {
|
|
72
173
|
gitignore: true,
|
|
73
|
-
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/docs/**"]
|
|
174
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/docs/**", "**/.docstodev/**"]
|
|
74
175
|
});
|
|
176
|
+
|
|
177
|
+
const sourceFiles = files.filter(f => {
|
|
178
|
+
const ext = path.extname(f);
|
|
179
|
+
return supportedExts.includes(ext);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
console.log(` ✓ ${files.length} fichiers détectés (${sourceFiles.length} fichiers sources)\n`);
|
|
75
183
|
|
|
76
184
|
const docsDir = "docs";
|
|
77
|
-
if (!existsSync(docsDir))
|
|
185
|
+
if (!existsSync(docsDir)) {
|
|
186
|
+
console.log("📁 Création du dossier 'docs'...");
|
|
187
|
+
mkdirSync(docsDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let filesToAnalyze = sourceFiles;
|
|
191
|
+
let cachedCount = 0;
|
|
192
|
+
|
|
193
|
+
if (options.incremental) {
|
|
194
|
+
console.log("⚡ Mode incrémental activé - Détection des modifications...");
|
|
195
|
+
const modifiedFiles = cache.getModifiedFiles(sourceFiles);
|
|
196
|
+
filesToAnalyze = modifiedFiles;
|
|
197
|
+
cachedCount = sourceFiles.length - modifiedFiles.length;
|
|
198
|
+
console.log(` ✓ ${modifiedFiles.length} fichiers modifiés, ${cachedCount} en cache\n`);
|
|
199
|
+
}
|
|
78
200
|
|
|
79
|
-
|
|
201
|
+
console.log("🔍 Étape 2/6 : Analyse approfondie des fichiers sources...");
|
|
202
|
+
let aiContext = `Tu es un expert en analyse de code. Analyse ces fichiers et explique leur architecture, leurs responsabilités et comment ils collaborent ensemble. Identifie le rôle de chaque fichier (Page, Component, Service, API, Utility, etc.). Sois précis et technique.\n\n`;
|
|
80
203
|
let detailContent = `## 🔬 Analyse détaillée des composants\n\n`;
|
|
81
|
-
let mermaidGraph = "";
|
|
204
|
+
let mermaidGraph = "graph LR\n";
|
|
82
205
|
const allDeps = new Map<string, string>();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
206
|
+
const analyses: FileAnalysis[] = [];
|
|
207
|
+
|
|
208
|
+
let processedCount = 0;
|
|
209
|
+
|
|
210
|
+
if (options.incremental) {
|
|
211
|
+
for (const file of sourceFiles) {
|
|
212
|
+
if (!filesToAnalyze.includes(file)) {
|
|
213
|
+
const cached = cache.get(file);
|
|
214
|
+
if (cached) {
|
|
215
|
+
analyses.push(cached.analysis);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const file of filesToAnalyze) {
|
|
222
|
+
processedCount++;
|
|
223
|
+
if (processedCount % 5 === 0) {
|
|
224
|
+
process.stdout.write(` Analysé: ${processedCount}/${filesToAnalyze.length} fichiers...\r`);
|
|
225
|
+
}
|
|
87
226
|
|
|
88
227
|
const content = readFileSync(file, "utf-8");
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
detailContent +=
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
228
|
+
const analysis = analyzeFile(file, content);
|
|
229
|
+
analyses.push(analysis);
|
|
230
|
+
|
|
231
|
+
if (options.incremental) {
|
|
232
|
+
cache.set(file, content, analysis);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const fileNameSanitized = path.basename(file).replace(/[.\-]/g, '_');
|
|
236
|
+
|
|
237
|
+
detailContent += `### 📄 [\`${file}\`](../${file.replace(/\\/g, '/')})\n\n`;
|
|
238
|
+
detailContent += `**${t.density}** : ${analysis.lines} lignes • ${getComplexityLevel(analysis.complexity)} (${analysis.complexity})\n\n`;
|
|
239
|
+
|
|
240
|
+
if (analysis.functions.length > 0) {
|
|
241
|
+
detailContent += `**${t.functions}** : ${analysis.functions.map(f => `\`${f}()\``).join(", ")}\n\n`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (analysis.classes.length > 0) {
|
|
245
|
+
detailContent += `**${t.classes}** : ${analysis.classes.map(c => `\`${c}\``).join(", ")}\n\n`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (analysis.types.length > 0) {
|
|
249
|
+
detailContent += `**${t.types}** : ${analysis.types.map(t => `\`${t}\``).join(", ")}\n\n`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (analysis.exports.length > 0) {
|
|
253
|
+
detailContent += `**${t.exports}** : ${analysis.exports.map(e => `\`${e}\``).join(", ")}\n\n`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (analysis.imports.length > 0) {
|
|
257
|
+
detailContent += `**${t.imports}** :\n\n`;
|
|
258
|
+
analysis.imports.forEach(imp => {
|
|
259
|
+
allDeps.set(imp.name, imp.type);
|
|
260
|
+
const usageInfo = imp.usage ? ` → utilise \`${imp.usage}\`` : '';
|
|
261
|
+
detailContent += `• \`${imp.name}\` (${imp.type})${usageInfo}\n`;
|
|
262
|
+
|
|
263
|
+
if (imp.type === 'Interne') {
|
|
264
|
+
const targetName = path.basename(imp.name).replace(/[.\-]/g, '_');
|
|
265
|
+
mermaidGraph += ` ${fileNameSanitized} --> ${targetName}\n`;
|
|
117
266
|
}
|
|
118
267
|
});
|
|
268
|
+
detailContent += `\n`;
|
|
119
269
|
}
|
|
120
|
-
|
|
121
|
-
|
|
270
|
+
|
|
271
|
+
detailContent += `---\n\n`;
|
|
272
|
+
|
|
273
|
+
aiContext += `═══ FICHIER: ${file} ═══\n`;
|
|
274
|
+
aiContext += `Langage: ${path.extname(file)}\n`;
|
|
275
|
+
aiContext += `Complexité: ${analysis.complexity}\n`;
|
|
276
|
+
aiContext += `Fonctions: ${analysis.functions.join(", ") || "Aucune"}\n`;
|
|
277
|
+
aiContext += `Classes: ${analysis.classes.join(", ") || "Aucune"}\n`;
|
|
278
|
+
aiContext += `Exports: ${analysis.exports.join(", ") || "Aucun"}\n`;
|
|
279
|
+
aiContext += `Dépendances: ${analysis.imports.map(i => i.name).join(", ")}\n`;
|
|
280
|
+
aiContext += `\nExtrait du code:\n${content.split(/\r?\n/).slice(0, 50).join("\n")}\n\n`;
|
|
122
281
|
}
|
|
282
|
+
|
|
283
|
+
const totalAnalyzed = processedCount + cachedCount;
|
|
284
|
+
console.log(` ✓ ${totalAnalyzed} fichiers analysés (${processedCount} nouvelles analyses, ${cachedCount} depuis cache)\n`);
|
|
123
285
|
|
|
124
|
-
|
|
286
|
+
console.log("🤖 Étape 3/6 : Génération de l'analyse intelligente (IA)...");
|
|
287
|
+
const aiSummary = await askAI(aiContext) || "⚠️ Analyse IA indisponible. Vérifiez votre configuration.";
|
|
288
|
+
const formattedAiSummary = formatAISummary(aiSummary);
|
|
289
|
+
console.log(` ✓ Synthèse IA générée\n`);
|
|
125
290
|
|
|
291
|
+
console.log("📝 Étape 4/6 : Construction du rapport Markdown...");
|
|
126
292
|
let finalMD = `# ${t.reportTitle}\n\n`;
|
|
127
|
-
finalMD += `> 📅 ${t.genAt} : ${new Date().toLocaleString()}\n
|
|
128
|
-
finalMD +=
|
|
129
|
-
|
|
293
|
+
finalMD += `> 📅 ${t.genAt} : ${new Date().toLocaleString("fr-FR", { dateStyle: "full", timeStyle: "short" })}\n`;
|
|
294
|
+
finalMD += `> 📊 ${totalAnalyzed} fichiers analysés • ${allDeps.size} dépendances identifiées\n`;
|
|
295
|
+
if (options.incremental && cachedCount > 0) {
|
|
296
|
+
finalMD += `> ⚡ Mode incrémental : ${cachedCount} fichiers depuis cache\n`;
|
|
297
|
+
}
|
|
298
|
+
finalMD += `\n`;
|
|
299
|
+
finalMD += `## 💡 ${t.aiTitle}\n\n${formattedAiSummary}\n\n`;
|
|
300
|
+
finalMD += `## 📂 ${t.structure}\n\n`;
|
|
301
|
+
finalMD += "```\n";
|
|
302
|
+
finalMD += renderTree(buildTree(files));
|
|
303
|
+
finalMD += "```\n\n";
|
|
130
304
|
finalMD += detailContent;
|
|
131
|
-
finalMD += `## 📦 ${t.deps}\n\n
|
|
132
|
-
|
|
305
|
+
finalMD += `## 📦 ${t.deps}\n\n`;
|
|
306
|
+
finalMD += `| Module | Type | Occurrences |\n`;
|
|
307
|
+
finalMD += `| :--- | :--- | :---: |\n`;
|
|
308
|
+
|
|
309
|
+
const depCount = new Map<string, number>();
|
|
310
|
+
analyses.forEach(a => {
|
|
311
|
+
a.imports.forEach(imp => {
|
|
312
|
+
depCount.set(imp.name, (depCount.get(imp.name) || 0) + 1);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
Array.from(allDeps.entries())
|
|
317
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
318
|
+
.forEach(([name, type]) => {
|
|
319
|
+
finalMD += `| \`${name}\` | ${type} | ${depCount.get(name) || 1} |\n`;
|
|
320
|
+
});
|
|
133
321
|
|
|
134
322
|
writeFileSync(path.join(docsDir, "docs-to-dev.md"), finalMD);
|
|
135
|
-
|
|
323
|
+
console.log(` ✓ Rapport Markdown généré : docs/docs-to-dev.md\n`);
|
|
324
|
+
|
|
325
|
+
console.log("🎨 Étape 5/6 : Export HTML avec graphiques...");
|
|
326
|
+
const fileTreeStructure = buildTree(files);
|
|
327
|
+
exportToHTML(docsDir, finalMD, mermaidGraph, language, fileTreeStructure);
|
|
328
|
+
console.log(` ✓ Rapport HTML généré : docs/report.html\n`);
|
|
136
329
|
|
|
137
|
-
console.log("📄 Génération du PDF
|
|
330
|
+
console.log("📄 Étape 6/6 : Génération du PDF...");
|
|
138
331
|
await generatePDF(path.join(docsDir, "report.html"), path.join(docsDir, "report.pdf"));
|
|
332
|
+
console.log(` ✓ PDF exporté : docs/report.pdf\n`);
|
|
333
|
+
|
|
334
|
+
console.log("╔══════════════════════════════════════════╗");
|
|
335
|
+
console.log("║ ✨ Analyse terminée avec succès ! ║");
|
|
336
|
+
console.log("╚══════════════════════════════════════════╝\n");
|
|
337
|
+
console.log(`📊 Statistiques :`);
|
|
338
|
+
console.log(` • ${files.length} fichiers scannés`);
|
|
339
|
+
console.log(` • ${totalAnalyzed} fichiers analysés`);
|
|
340
|
+
if (options.incremental) {
|
|
341
|
+
console.log(` • ${cachedCount} fichiers depuis cache`);
|
|
342
|
+
}
|
|
343
|
+
console.log(` • ${allDeps.size} dépendances uniques`);
|
|
344
|
+
const avgComp = analyses.length > 0 ? Math.round(analyses.reduce((sum, a) => sum + a.complexity, 0) / analyses.length) : 0;
|
|
345
|
+
console.log(` • Complexité moyenne : ${avgComp}`);
|
|
346
|
+
|
|
347
|
+
if (options.incremental) {
|
|
348
|
+
const cacheStats = cache.getStats();
|
|
349
|
+
console.log(`\n💾 Cache :`);
|
|
350
|
+
console.log(` • ${cacheStats.total} entrées`);
|
|
351
|
+
console.log(` • ${Math.round(cacheStats.size / 1024)} KB`);
|
|
352
|
+
}
|
|
353
|
+
console.log("");
|
|
139
354
|
|
|
140
355
|
return files.length;
|
|
141
356
|
}
|
|
142
357
|
|
|
143
|
-
async function generatePDF(htmlPath: string, outputPath: string) {
|
|
358
|
+
async function generatePDF(htmlPath: string, outputPath: string): Promise<void> {
|
|
359
|
+
let browser: Browser | null = null;
|
|
144
360
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
361
|
+
browser = await puppeteer.launch({
|
|
362
|
+
headless: true,
|
|
363
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
364
|
+
});
|
|
365
|
+
const page: Page = await browser.newPage();
|
|
366
|
+
|
|
367
|
+
await page.goto(`file://${path.resolve(htmlPath)}`, {
|
|
368
|
+
waitUntil: "networkidle0",
|
|
369
|
+
timeout: 30000
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
await page.waitForFunction(() => {
|
|
373
|
+
return typeof (window as any).mermaid !== 'undefined';
|
|
374
|
+
}, { timeout: 5000 }).catch(() => {});
|
|
375
|
+
|
|
376
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
377
|
+
|
|
148
378
|
await page.pdf({
|
|
149
379
|
path: outputPath,
|
|
150
380
|
format: 'A4',
|
|
151
381
|
printBackground: true,
|
|
152
|
-
margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' }
|
|
382
|
+
margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },
|
|
383
|
+
preferCSSPageSize: true
|
|
153
384
|
});
|
|
385
|
+
|
|
154
386
|
await browser.close();
|
|
155
|
-
} catch (e) {
|
|
387
|
+
} catch (e) {
|
|
388
|
+
console.error(" ⚠️ Erreur lors de la génération du PDF:", e);
|
|
389
|
+
if (browser) {
|
|
390
|
+
await browser.close().catch(() => {});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
156
393
|
}
|