docstodev 1.0.0 → 1.0.2
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/dist/ai/analyzer.d.ts.map +1 -1
- package/dist/ai/analyzer.js +2 -1
- package/dist/ai/analyzer.js.map +1 -1
- package/dist/analyzers/languageAnalyzer.d.ts +18 -0
- package/dist/analyzers/languageAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/languageAnalyzer.js +251 -0
- package/dist/analyzers/languageAnalyzer.js.map +1 -0
- package/dist/cache/cacheManager.d.ts +38 -0
- package/dist/cache/cacheManager.d.ts.map +1 -0
- package/dist/cache/cacheManager.js +141 -0
- package/dist/cache/cacheManager.js.map +1 -0
- package/dist/cli/index.js +279 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/run.d.ts +4 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +250 -66
- package/dist/commands/run.js.map +1 -1
- package/dist/exporters/html.d.ts +5 -1
- package/dist/exporters/html.d.ts.map +1 -1
- package/dist/exporters/html.js +477 -108
- package/dist/exporters/html.js.map +1 -1
- package/package.json +2 -2
- package/src/ai/analyzer.ts +3 -1
- package/src/analyzers/languageAnalyzer.ts +288 -0
- package/src/cache/cacheManager.ts +179 -0
- package/src/cli/index.ts +307 -23
- package/src/commands/run.ts +312 -75
- package/src/exporters/html.ts +521 -105
- 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/index.ts +0 -0
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
|
}
|