@silvestv/migration-planificator 3.0.0
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/LICENSE +96 -0
- package/README.fr.md +359 -0
- package/README.md +360 -0
- package/SECURITY.md +187 -0
- package/dist/client.bundle.js +357 -0
- package/dist/src/core/app-analyzer.js +134 -0
- package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
- package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
- package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
- package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
- package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
- package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
- package/dist/src/core/ast/matchers/html/index.js +118 -0
- package/dist/src/core/ast/matchers/index.js +377 -0
- package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
- package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
- package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
- package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
- package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
- package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
- package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
- package/dist/src/core/ast/matchers/ts/index.js +53 -0
- package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
- package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
- package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
- package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
- package/dist/src/core/ast/scanner-ast.js +444 -0
- package/dist/src/core/project-detector.js +196 -0
- package/dist/src/core/project-strategy/index.js +9 -0
- package/dist/src/core/project-strategy/nx-strategy.js +130 -0
- package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
- package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
- package/dist/src/core/project-strategy/strategy-factory.js +15 -0
- package/dist/src/core/rules-loader.js +89 -0
- package/dist/src/core/scan-reporter.js +316 -0
- package/dist/src/core/scanner-delta.js +339 -0
- package/dist/src/core/scanner-orchestrator.js +266 -0
- package/dist/src/core/scanner-regex.js +298 -0
- package/dist/src/core/workload/calculator.js +82 -0
- package/dist/src/core/workload/constants.js +15 -0
- package/dist/src/core/workload/grouping.js +18 -0
- package/dist/src/core/workload/hierarchy-calculator.js +127 -0
- package/dist/src/core/workload/index.js +11 -0
- package/dist/src/core/workload/metadata.js +20 -0
- package/dist/src/core/workload/special-workload.js +101 -0
- package/dist/src/core/workload/target-resolver.js +34 -0
- package/dist/src/data/angular-migration-rules.json +2337 -0
- package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
- package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
- package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
- package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
- package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
- package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
- package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
- package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
- package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
- package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
- package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
- package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
- package/dist/src/index.js +161 -0
- package/dist/src/models/chip-config.js +45 -0
- package/dist/src/models/interfaces/app-details.interface.js +2 -0
- package/dist/src/models/interfaces/ast-interfaces.js +5 -0
- package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
- package/dist/src/models/interfaces/client-interfaces.js +6 -0
- package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
- package/dist/src/models/interfaces/html-match.interface.js +2 -0
- package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
- package/dist/src/models/interfaces/lib-details.interface.js +2 -0
- package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
- package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
- package/dist/src/models/interfaces/project-info.interface.js +2 -0
- package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
- package/dist/src/models/interfaces/rule-match.interface.js +2 -0
- package/dist/src/models/interfaces/rule.interface.js +2 -0
- package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
- package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
- package/dist/src/models/interfaces/special-workload.interface.js +2 -0
- package/dist/src/models/interfaces/workload-report.interface.js +2 -0
- package/dist/src/models/types/build-block-blob.type.js +2 -0
- package/dist/src/models/types/migration-version.type.js +2 -0
- package/dist/src/models/types/project-type.type.js +2 -0
- package/dist/src/models/types/risk-level.type.js +2 -0
- package/dist/src/models/types/rule-category.type.js +2 -0
- package/dist/src/models/types/rule-priority.type.js +2 -0
- package/dist/src/models/types/rule-workload-type.type.js +2 -0
- package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
- package/dist/src/templates/landing/card-app-info.template.js +63 -0
- package/dist/src/templates/landing/card-lib-info.template.js +67 -0
- package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
- package/dist/src/templates/landing/nx-summary.template.js +115 -0
- package/dist/src/templates/landing/project-overview.template.js +27 -0
- package/dist/src/templates/page/index-page.template.js +95 -0
- package/dist/src/templates/page/main.template.js +83 -0
- package/dist/src/templates/page/migration-guide.template.js +175 -0
- package/dist/src/templates/page/workload-report.template.js +53 -0
- package/dist/src/templates/workload/dashboard.template.js +184 -0
- package/dist/src/templates/workload/filters-panel.template.js +215 -0
- package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
- package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
- package/dist/src/templates/workload/hierarchy-shared.js +163 -0
- package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
- package/dist/src/templates/workload/hierarchy.template.js +35 -0
- package/dist/src/templates/workload/rule-modal.template.js +280 -0
- package/dist/src/utils/core/args-parser.js +123 -0
- package/dist/src/utils/core/array-helpers.js +18 -0
- package/dist/src/utils/core/ast-helpers.js +99 -0
- package/dist/src/utils/core/file-helpers.js +109 -0
- package/dist/src/utils/core/html-helpers.js +36 -0
- package/dist/src/utils/core/index.js +28 -0
- package/dist/src/utils/core/logger.js +38 -0
- package/dist/src/utils/core/rule-helpers.js +15 -0
- package/dist/src/utils/core/workload-formatter.js +6 -0
- package/dist/src/utils/shared/array-helpers.js +25 -0
- package/dist/src/utils/shared/date-helpers.js +109 -0
- package/dist/src/utils/shared/html-helpers.js +37 -0
- package/dist/src/utils/shared/index.js +25 -0
- package/dist/src/utils/shared/rule-helpers.js +20 -0
- package/dist/src/utils/shared/time-formatters.js +76 -0
- package/dist/styles.css +2 -0
- package/package.json +107 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Template pour modals de détails de règles
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderRuleModals = renderRuleModals;
|
|
7
|
+
exports.renderDescriptionSection = renderDescriptionSection;
|
|
8
|
+
exports.renderCodeExamplesSection = renderCodeExamplesSection;
|
|
9
|
+
exports.renderMigrationCommandSection = renderMigrationCommandSection;
|
|
10
|
+
exports.renderDocumentationSection = renderDocumentationSection;
|
|
11
|
+
exports.renderAffectedFilesSection = renderAffectedFilesSection;
|
|
12
|
+
exports.renderMetadataSection = renderMetadataSection;
|
|
13
|
+
const shared_1 = require("../../utils/shared");
|
|
14
|
+
/**
|
|
15
|
+
* Génère tous les modals pour les règles
|
|
16
|
+
*/
|
|
17
|
+
function renderRuleModals(rules, matches) {
|
|
18
|
+
// Grouper matches par ruleKey
|
|
19
|
+
const matchesByRule = new Map();
|
|
20
|
+
matches.forEach(match => {
|
|
21
|
+
if (!matchesByRule.has(match.ruleKey)) {
|
|
22
|
+
matchesByRule.set(match.ruleKey, []);
|
|
23
|
+
}
|
|
24
|
+
matchesByRule.get(match.ruleKey).push(match);
|
|
25
|
+
});
|
|
26
|
+
return rules.map(rule => renderRuleModal(rule, matchesByRule.get(rule.key) || [])).join('');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Génère le HTML d'une modal pour une règle
|
|
30
|
+
*/
|
|
31
|
+
function renderRuleModal(rule, matches) {
|
|
32
|
+
const modalId = `modal-${rule.key}`;
|
|
33
|
+
const riskColor = getRiskColor(rule.risk_level);
|
|
34
|
+
return `
|
|
35
|
+
<div id="${modalId}" class="fixed inset-0 z-50 hidden">
|
|
36
|
+
<div class="fixed inset-0 bg-black bg-opacity-50" data-modal-overlay></div>
|
|
37
|
+
<div class="fixed inset-0 flex items-center justify-center p-4">
|
|
38
|
+
<div class="bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-auto relative">
|
|
39
|
+
${renderModalHeader(rule, riskColor)}
|
|
40
|
+
${renderModalBody(rule, matches)}
|
|
41
|
+
${renderModalFooter()}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Retourne la couleur du badge selon le niveau de risque
|
|
49
|
+
*/
|
|
50
|
+
function getRiskColor(riskLevel) {
|
|
51
|
+
const colorMap = {
|
|
52
|
+
critical: 'red',
|
|
53
|
+
high: 'orange',
|
|
54
|
+
medium: 'yellow',
|
|
55
|
+
low: 'green'
|
|
56
|
+
};
|
|
57
|
+
return colorMap[riskLevel] || 'gray';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Génère le header de la modal
|
|
61
|
+
*/
|
|
62
|
+
function renderModalHeader(rule, riskColor) {
|
|
63
|
+
return `
|
|
64
|
+
<div class="sticky top-0 bg-white border-b border-gray-200 p-6 flex justify-between items-start z-10">
|
|
65
|
+
<div class="flex-1">
|
|
66
|
+
<h2 class="text-2xl font-bold text-gray-800 mb-2">${(0, shared_1.escapeHtml)(rule.summary)}</h2>
|
|
67
|
+
<div class="flex items-center space-x-2">
|
|
68
|
+
${(0, shared_1.renderBadge)(rule.risk_level.toUpperCase(), riskColor)}
|
|
69
|
+
${(0, shared_1.renderBadge)(rule.category, 'blue')}
|
|
70
|
+
${rule.auto_fixable ? (0, shared_1.renderBadge)('Auto-fixable', 'green') : ''}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<button data-modal-close class="text-gray-400 hover:text-gray-600 text-3xl font-bold leading-none">
|
|
74
|
+
×
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Génère le body de la modal
|
|
81
|
+
*/
|
|
82
|
+
function renderModalBody(rule, matches) {
|
|
83
|
+
return `
|
|
84
|
+
<div class="p-6 space-y-6">
|
|
85
|
+
${renderDescriptionSection(rule)}
|
|
86
|
+
${renderCodeExamplesSection(rule)}
|
|
87
|
+
${renderMigrationCommandSection(rule)}
|
|
88
|
+
${renderDocumentationSection(rule)}
|
|
89
|
+
${renderAffectedFilesSection(matches)}
|
|
90
|
+
${renderMetadataSection(rule)}
|
|
91
|
+
</div>
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Section Description
|
|
96
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
97
|
+
*/
|
|
98
|
+
function renderDescriptionSection(rule) {
|
|
99
|
+
return `
|
|
100
|
+
<section>
|
|
101
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">📋 Description</h3>
|
|
102
|
+
<p class="text-gray-700 whitespace-pre-line">${(0, shared_1.escapeHtml)(rule.description)}</p>
|
|
103
|
+
</section>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Section Exemples de Code
|
|
108
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
109
|
+
*/
|
|
110
|
+
function renderCodeExamplesSection(rule) {
|
|
111
|
+
if (!rule.code_description)
|
|
112
|
+
return '';
|
|
113
|
+
return `
|
|
114
|
+
<section>
|
|
115
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">💻 Exemples de Code</h3>
|
|
116
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
117
|
+
<pre class="text-sm text-gray-800 overflow-x-auto whitespace-pre-wrap">${(0, shared_1.escapeHtml)(rule.code_description)}</pre>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Section Commande de Migration
|
|
124
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
125
|
+
*/
|
|
126
|
+
function renderMigrationCommandSection(rule) {
|
|
127
|
+
if (!rule.migration_command)
|
|
128
|
+
return '';
|
|
129
|
+
// Échapper les guillemets pour éviter les problèmes JavaScript
|
|
130
|
+
const escapedCommand = rule.migration_command.replace(/'/g, "\\'");
|
|
131
|
+
return `
|
|
132
|
+
<section>
|
|
133
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">⚡ Commande de Migration</h3>
|
|
134
|
+
<div class="flex items-center space-x-2">
|
|
135
|
+
<code class="flex-1 bg-gray-800 text-green-400 p-3 rounded-lg font-mono text-sm">${(0, shared_1.escapeHtml)(rule.migration_command)}</code>
|
|
136
|
+
<button
|
|
137
|
+
onclick="window.copyRuleCommand(this, '${escapedCommand}')"
|
|
138
|
+
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
|
139
|
+
title="Copier dans le presse-papier"
|
|
140
|
+
>
|
|
141
|
+
Copier
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Section Documentation
|
|
149
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
150
|
+
*/
|
|
151
|
+
function renderDocumentationSection(rule) {
|
|
152
|
+
if (!rule.doc_url)
|
|
153
|
+
return '';
|
|
154
|
+
return `
|
|
155
|
+
<section>
|
|
156
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">📚 Documentation</h3>
|
|
157
|
+
<a
|
|
158
|
+
href="${(0, shared_1.escapeHtml)(rule.doc_url)}"
|
|
159
|
+
target="_blank"
|
|
160
|
+
rel="noopener noreferrer"
|
|
161
|
+
class="inline-flex items-center text-blue-600 hover:text-blue-800 underline"
|
|
162
|
+
>
|
|
163
|
+
${(0, shared_1.escapeHtml)(rule.doc_url)}
|
|
164
|
+
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
165
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
166
|
+
</svg>
|
|
167
|
+
</a>
|
|
168
|
+
</section>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Section Fichiers Impactés
|
|
173
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
174
|
+
*/
|
|
175
|
+
function renderAffectedFilesSection(matches) {
|
|
176
|
+
if (matches.length === 0) {
|
|
177
|
+
return `
|
|
178
|
+
<section>
|
|
179
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">📂 Fichiers Impactés</h3>
|
|
180
|
+
<p class="text-gray-500 italic">Aucune occurrence détectée dans le projet.</p>
|
|
181
|
+
</section>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
return `
|
|
185
|
+
<section>
|
|
186
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">📂 Fichiers Impactés (${matches.length})</h3>
|
|
187
|
+
<div class="bg-gray-50 rounded-lg p-4 max-h-64 overflow-y-auto">
|
|
188
|
+
<ul class="space-y-2">
|
|
189
|
+
${matches.map(match => renderMatchItem(match)).join('')}
|
|
190
|
+
</ul>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Item de match (fichier impacté)
|
|
197
|
+
*/
|
|
198
|
+
function renderMatchItem(match) {
|
|
199
|
+
const truncatedText = match.matchedText.substring(0, 100);
|
|
200
|
+
const displayText = match.matchedText.length > 100 ? `${truncatedText}...` : truncatedText;
|
|
201
|
+
// Formater le chemin pour VSCode (forward slashes, lowercase drive letter)
|
|
202
|
+
const vscodePath = formatVSCodePath(match.filePath);
|
|
203
|
+
return `
|
|
204
|
+
<li class="text-sm">
|
|
205
|
+
<div class="flex items-start justify-between">
|
|
206
|
+
<div class="flex-1">
|
|
207
|
+
<code class="text-blue-600">${(0, shared_1.escapeHtml)(match.filePath)}:${match.lineNumber}</code>
|
|
208
|
+
<p class="text-gray-600 mt-1 ml-4 text-xs font-mono bg-white p-2 rounded">${(0, shared_1.escapeHtml)(displayText)}</p>
|
|
209
|
+
</div>
|
|
210
|
+
<a
|
|
211
|
+
href="vscode://file/${vscodePath}:${match.lineNumber}"
|
|
212
|
+
class="px-3 py-1.5 text-xs bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium shadow-sm"
|
|
213
|
+
title="Ouvrir dans VSCode à la ligne ${match.lineNumber}"
|
|
214
|
+
>
|
|
215
|
+
📝 Ouvrir
|
|
216
|
+
</a>
|
|
217
|
+
</div>
|
|
218
|
+
</li>
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Formate un chemin Windows pour VSCode URI
|
|
223
|
+
* VSCode nécessite: forward slashes + lowercase drive letter
|
|
224
|
+
* Exemple: C:\Users\file.ts -> c:/Users/file.ts
|
|
225
|
+
*/
|
|
226
|
+
function formatVSCodePath(filePath) {
|
|
227
|
+
// Remplacer backslashes par forward slashes
|
|
228
|
+
let formatted = filePath.replace(/\\/g, '/');
|
|
229
|
+
// Convertir la lettre de lecteur en minuscule (C: -> c:)
|
|
230
|
+
// Format: "C:/path" ou "C:\path" -> "c:/path"
|
|
231
|
+
const driveLetterMatch = formatted.match(/^([A-Z]):/);
|
|
232
|
+
if (driveLetterMatch) {
|
|
233
|
+
formatted = driveLetterMatch[1].toLowerCase() + formatted.substring(1);
|
|
234
|
+
}
|
|
235
|
+
return formatted;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Section Métadonnées
|
|
239
|
+
* Exportée pour réutilisation dans guide-rule-card.template.ts
|
|
240
|
+
*/
|
|
241
|
+
function renderMetadataSection(rule) {
|
|
242
|
+
return `
|
|
243
|
+
<section class="border-t border-gray-200 pt-4">
|
|
244
|
+
<h3 class="text-lg font-bold text-gray-800 mb-2">🔍 Métadonnées</h3>
|
|
245
|
+
<div class="grid grid-cols-2 gap-3 text-sm">
|
|
246
|
+
<div>
|
|
247
|
+
<span class="font-semibold text-gray-700">Clé règle :</span>
|
|
248
|
+
<code class="ml-2 text-gray-600">${(0, shared_1.escapeHtml)(rule.key)}</code>
|
|
249
|
+
</div>
|
|
250
|
+
<div>
|
|
251
|
+
<span class="font-semibold text-gray-700">Temps estimé :</span>
|
|
252
|
+
<span class="ml-2 text-gray-600">${rule.estimated_time_per_occurrence} min/occurrence</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div>
|
|
255
|
+
<span class="font-semibold text-gray-700">Types fichiers :</span>
|
|
256
|
+
<span class="ml-2 text-gray-600">${rule.fileTypes.join(', ')}</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<span class="font-semibold text-gray-700">Auto-fixable :</span>
|
|
260
|
+
<span class="ml-2 text-gray-600">${rule.auto_fixable ? '✅ Oui' : '❌ Non'}</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</section>
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Footer de la modal
|
|
268
|
+
*/
|
|
269
|
+
function renderModalFooter() {
|
|
270
|
+
return `
|
|
271
|
+
<div class="sticky bottom-0 bg-gray-50 border-t border-gray-200 p-4 flex justify-end">
|
|
272
|
+
<button
|
|
273
|
+
data-modal-close
|
|
274
|
+
class="px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
|
275
|
+
>
|
|
276
|
+
Fermer
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseArgs = parseArgs;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
/**
|
|
39
|
+
* Parse les arguments de la ligne de commande
|
|
40
|
+
* Supporte: --project-path=path, --scanner=ast|regex|both, --rules=[18,19,20] ou --rules=all
|
|
41
|
+
*/
|
|
42
|
+
function parseArgs(args) {
|
|
43
|
+
let projectPath;
|
|
44
|
+
let scannerType = 'ast'; // Défaut: AST
|
|
45
|
+
let rules = ['to18', 'to19', 'to20']; // Défaut: all
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
const arg = args[i];
|
|
48
|
+
// Support ancien format --project et nouveau --project-path
|
|
49
|
+
if (arg.startsWith('--project-path=')) {
|
|
50
|
+
projectPath = arg.substring('--project-path='.length);
|
|
51
|
+
}
|
|
52
|
+
else if (arg.startsWith('--project=')) {
|
|
53
|
+
projectPath = arg.substring('--project='.length);
|
|
54
|
+
}
|
|
55
|
+
else if ((arg === '--project-path' || arg === '--project') && i + 1 < args.length) {
|
|
56
|
+
projectPath = args[i + 1];
|
|
57
|
+
i++;
|
|
58
|
+
}
|
|
59
|
+
// Parse scanner type
|
|
60
|
+
if (arg.startsWith('--scanner=')) {
|
|
61
|
+
const value = arg.substring('--scanner='.length);
|
|
62
|
+
if (['ast', 'regex', 'both'].includes(value)) {
|
|
63
|
+
scannerType = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Parse rules
|
|
67
|
+
if (arg.startsWith('--rules=')) {
|
|
68
|
+
const value = arg.substring('--rules='.length);
|
|
69
|
+
rules = parseRulesArgument(value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!projectPath) {
|
|
73
|
+
projectPath = process.cwd();
|
|
74
|
+
}
|
|
75
|
+
const resolvedPath = path.isAbsolute(projectPath)
|
|
76
|
+
? projectPath
|
|
77
|
+
: path.resolve(process.cwd(), projectPath);
|
|
78
|
+
return {
|
|
79
|
+
projectPath: resolvedPath,
|
|
80
|
+
scannerType,
|
|
81
|
+
rules
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse l'argument --rules
|
|
86
|
+
* Exemples:
|
|
87
|
+
* --rules=18 → ['to18']
|
|
88
|
+
* --rules=[18] → ['to18']
|
|
89
|
+
* --rules=[18,19] → ['to18', 'to19']
|
|
90
|
+
* --rules=[18,19,20] → ['to18', 'to19', 'to20']
|
|
91
|
+
* --rules=all → ['to18', 'to19', 'to20']
|
|
92
|
+
*/
|
|
93
|
+
function parseRulesArgument(value) {
|
|
94
|
+
// Cas spécial: all
|
|
95
|
+
if (value === 'all') {
|
|
96
|
+
return ['to18', 'to19', 'to20'];
|
|
97
|
+
}
|
|
98
|
+
// Nettoyer les crochets si présents
|
|
99
|
+
const cleaned = value.replace(/[\[\]]/g, '');
|
|
100
|
+
// Séparer par virgule et filtrer
|
|
101
|
+
const versions = cleaned
|
|
102
|
+
.split(',')
|
|
103
|
+
.map(v => v.trim())
|
|
104
|
+
.filter(v => v.length > 0);
|
|
105
|
+
// Convertir en MigrationVersion
|
|
106
|
+
const result = [];
|
|
107
|
+
for (const version of versions) {
|
|
108
|
+
if (version === '18') {
|
|
109
|
+
result.push('to18');
|
|
110
|
+
}
|
|
111
|
+
else if (version === '19') {
|
|
112
|
+
result.push('to19');
|
|
113
|
+
}
|
|
114
|
+
else if (version === '20') {
|
|
115
|
+
result.push('to20');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Si aucune version valide, retourner all par défaut
|
|
119
|
+
if (result.length === 0) {
|
|
120
|
+
return ['to18', 'to19', 'to20'];
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.groupBy = void 0;
|
|
4
|
+
exports.chunk = chunk;
|
|
5
|
+
/**
|
|
6
|
+
* Divise un tableau en chunks de taille fixe
|
|
7
|
+
* Utilisé pour le batch processing des fichiers AST
|
|
8
|
+
*/
|
|
9
|
+
function chunk(array, size) {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for (let i = 0; i < array.length; i += size) {
|
|
12
|
+
chunks.push(array.slice(i, i + size));
|
|
13
|
+
}
|
|
14
|
+
return chunks;
|
|
15
|
+
}
|
|
16
|
+
// Re-export groupBy depuis shared pour rétrocompatibilité
|
|
17
|
+
var array_helpers_1 = require("../shared/array-helpers");
|
|
18
|
+
Object.defineProperty(exports, "groupBy", { enumerable: true, get: function () { return array_helpers_1.groupBy; } });
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findParentByTypes = findParentByTypes;
|
|
4
|
+
exports.isImportOrExportSpecifier = isImportOrExportSpecifier;
|
|
5
|
+
exports.extractModuleInfo = extractModuleInfo;
|
|
6
|
+
exports.findImportOrExportDeclaration = findImportOrExportDeclaration;
|
|
7
|
+
exports.getSpecifierName = getSpecifierName;
|
|
8
|
+
const ts_morph_1 = require("ts-morph");
|
|
9
|
+
/**
|
|
10
|
+
* Trouve un nœud parent correspondant à l'un des types spécifiés
|
|
11
|
+
* Remonte dans l'arbre AST jusqu'à trouver un match ou atteindre la racine
|
|
12
|
+
*
|
|
13
|
+
* @param node - Nœud de départ
|
|
14
|
+
* @param types - Liste des types de nœuds recherchés (ex: ['ImportDeclaration', 'ExportDeclaration'])
|
|
15
|
+
* @returns Le premier parent correspondant ou undefined
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Trouver l'ImportDeclaration parent d'un ImportSpecifier
|
|
19
|
+
* const importDecl = findParentByTypes(specifier, ['ImportDeclaration']);
|
|
20
|
+
*/
|
|
21
|
+
function findParentByTypes(node, types) {
|
|
22
|
+
let current = node.getParent();
|
|
23
|
+
while (current) {
|
|
24
|
+
const kindName = current.getKindName();
|
|
25
|
+
if (types.includes(kindName)) {
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
current = current.getParent();
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Vérifie si un nœud est un ImportSpecifier ou ExportSpecifier
|
|
34
|
+
* Utilitaire pour éviter la répétition de cette condition
|
|
35
|
+
*
|
|
36
|
+
* @param node - Nœud à vérifier
|
|
37
|
+
* @returns true si le nœud est ImportSpecifier ou ExportSpecifier
|
|
38
|
+
*/
|
|
39
|
+
function isImportOrExportSpecifier(node) {
|
|
40
|
+
return ts_morph_1.Node.isImportSpecifier(node) || ts_morph_1.Node.isExportSpecifier(node);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extrait les informations d'un module depuis une déclaration Import/Export
|
|
44
|
+
*
|
|
45
|
+
* @param declaration - ImportDeclaration ou ExportDeclaration
|
|
46
|
+
* @param specifier - ImportSpecifier ou ExportSpecifier
|
|
47
|
+
* @returns ModuleInfo ou undefined si extraction impossible
|
|
48
|
+
*/
|
|
49
|
+
function extractModuleInfo(declaration, specifier) {
|
|
50
|
+
if (!ts_morph_1.Node.isImportDeclaration(declaration) && !ts_morph_1.Node.isExportDeclaration(declaration)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const name = getSpecifierName(specifier);
|
|
54
|
+
const moduleSpecifier = declaration.getModuleSpecifierValue();
|
|
55
|
+
if (!name || !moduleSpecifier) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
moduleSpecifier,
|
|
60
|
+
sourceFile: declaration.getSourceFile(),
|
|
61
|
+
name
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Trouve le premier parent de type ImportDeclaration ou ExportDeclaration
|
|
66
|
+
* Cas d'usage spécifique pour la résolution de symboles cross-file
|
|
67
|
+
*
|
|
68
|
+
* @param node - Nœud de départ (généralement ImportSpecifier ou ExportSpecifier)
|
|
69
|
+
* @returns ImportDeclaration ou ExportDeclaration parent, ou undefined
|
|
70
|
+
*/
|
|
71
|
+
function findImportOrExportDeclaration(node) {
|
|
72
|
+
const parent = findParentByTypes(node, ['ImportDeclaration', 'ExportDeclaration']);
|
|
73
|
+
if (!parent) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (ts_morph_1.Node.isImportDeclaration(parent)) {
|
|
77
|
+
return parent;
|
|
78
|
+
}
|
|
79
|
+
if (ts_morph_1.Node.isExportDeclaration(parent)) {
|
|
80
|
+
return parent;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Obtient le nom d'un ImportSpecifier ou ExportSpecifier de manière type-safe
|
|
86
|
+
* Alternative à (node as any).getName()
|
|
87
|
+
*
|
|
88
|
+
* @param node - ImportSpecifier ou ExportSpecifier
|
|
89
|
+
* @returns Le nom du symbole ou undefined
|
|
90
|
+
*/
|
|
91
|
+
function getSpecifierName(node) {
|
|
92
|
+
if (ts_morph_1.Node.isImportSpecifier(node)) {
|
|
93
|
+
return node.getName();
|
|
94
|
+
}
|
|
95
|
+
if (ts_morph_1.Node.isExportSpecifier(node)) {
|
|
96
|
+
return node.getName();
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldIgnoreDir = shouldIgnoreDir;
|
|
4
|
+
exports.collectFiles = collectFiles;
|
|
5
|
+
exports.countFiles = countFiles;
|
|
6
|
+
exports.buildBlockToRegex = buildBlockToRegex;
|
|
7
|
+
/**
|
|
8
|
+
* Liste des dossiers à ignorer lors du scan de fichiers
|
|
9
|
+
*/
|
|
10
|
+
const IGNORED_DIRS = [
|
|
11
|
+
'node_modules',
|
|
12
|
+
'dist',
|
|
13
|
+
'.git',
|
|
14
|
+
'.angular',
|
|
15
|
+
'coverage',
|
|
16
|
+
'.nx',
|
|
17
|
+
'.idea',
|
|
18
|
+
'.vscode',
|
|
19
|
+
'tmp',
|
|
20
|
+
'temp'
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Vérifie si un dossier doit être ignoré lors du scan
|
|
24
|
+
*/
|
|
25
|
+
function shouldIgnoreDir(dirName) {
|
|
26
|
+
return IGNORED_DIRS.includes(dirName);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Collecte récursivement les fichiers correspondant à un pattern
|
|
30
|
+
*/
|
|
31
|
+
function collectFiles(dirPath, pattern) {
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
if (!fs.existsSync(dirPath)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const files = [];
|
|
38
|
+
function scanDir(dir) {
|
|
39
|
+
try {
|
|
40
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const fullPath = path.join(dir, entry.name);
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
if (!shouldIgnoreDir(entry.name)) {
|
|
45
|
+
scanDir(fullPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (entry.isFile() && pattern.test(entry.name)) {
|
|
49
|
+
files.push(fullPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignorer les erreurs de lecture
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
scanDir(dirPath);
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compte récursivement les fichiers correspondant à un pattern
|
|
62
|
+
*/
|
|
63
|
+
function countFiles(dirPath, pattern) {
|
|
64
|
+
return collectFiles(dirPath, pattern).length;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convertit un pattern glob (BuildBlockBlob) en RegExp
|
|
68
|
+
*
|
|
69
|
+
* @param pattern Pattern glob like "*.module.ts", "environment*.ts", "package.json"
|
|
70
|
+
* @returns RegExp pour matcher les noms de fichiers
|
|
71
|
+
*/
|
|
72
|
+
function buildBlockToRegex(pattern) {
|
|
73
|
+
// Échapper les caractères spéciaux regex (sauf * et /)
|
|
74
|
+
let regexStr = pattern
|
|
75
|
+
.replace(/\./g, '\\.') // . → \.
|
|
76
|
+
.replace(/\+/g, '\\+') // + → \+
|
|
77
|
+
.replace(/\?/g, '\\?') // ? → \?
|
|
78
|
+
.replace(/\^/g, '\\^') // ^ → \^
|
|
79
|
+
.replace(/\$/g, '\\$') // $ → \$
|
|
80
|
+
.replace(/\(/g, '\\(') // ( → \(
|
|
81
|
+
.replace(/\)/g, '\\)') // ) → \)
|
|
82
|
+
.replace(/\[/g, '\\[') // [ → \[
|
|
83
|
+
.replace(/\]/g, '\\]') // ] → \]
|
|
84
|
+
.replace(/\{/g, '\\{') // { → \{
|
|
85
|
+
.replace(/\}/g, '\\}') // } → \}
|
|
86
|
+
.replace(/\|/g, '\\|'); // | → \|
|
|
87
|
+
// Gérer les patterns glob
|
|
88
|
+
if (pattern.startsWith('**/')) {
|
|
89
|
+
// **/ → matcher n'importe où dans le chemin
|
|
90
|
+
regexStr = regexStr.replace(/^\*\*\//, '');
|
|
91
|
+
// * restant → .*
|
|
92
|
+
regexStr = regexStr.replace(/\*/g, '.*');
|
|
93
|
+
return new RegExp(regexStr + '$');
|
|
94
|
+
}
|
|
95
|
+
else if (pattern.startsWith('*.')) {
|
|
96
|
+
// *.ext → matcher extension à la fin
|
|
97
|
+
regexStr = regexStr.replace(/^\*/, '.*');
|
|
98
|
+
return new RegExp(regexStr + '$');
|
|
99
|
+
}
|
|
100
|
+
else if (pattern.includes('*')) {
|
|
101
|
+
// Contient * (ex: tsconfig*.json, environment*.ts)
|
|
102
|
+
regexStr = regexStr.replace(/\*/g, '.*');
|
|
103
|
+
return new RegExp('^' + regexStr + '$');
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Nom exact (ex: package.json, angular.json)
|
|
107
|
+
return new RegExp('^' + regexStr + '$');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helpers HTML pour génération serveur
|
|
4
|
+
* REFACTORED: Import depuis modules partagés pour éliminer la duplication (DRY)
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.renderBadge = exports.escapeJson = exports.escapeHtml = exports.formatTimeForAxis = exports.formatTime = void 0;
|
|
8
|
+
exports.formatDate = formatDate;
|
|
9
|
+
exports.calculateTotalTime = calculateTotalTime;
|
|
10
|
+
const shared_1 = require("../shared");
|
|
11
|
+
Object.defineProperty(exports, "formatTime", { enumerable: true, get: function () { return shared_1.formatTime; } });
|
|
12
|
+
Object.defineProperty(exports, "formatTimeForAxis", { enumerable: true, get: function () { return shared_1.formatTimeForAxis; } });
|
|
13
|
+
Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return shared_1.escapeHtml; } });
|
|
14
|
+
Object.defineProperty(exports, "escapeJson", { enumerable: true, get: function () { return shared_1.escapeJson; } });
|
|
15
|
+
Object.defineProperty(exports, "renderBadge", { enumerable: true, get: function () { return shared_1.renderBadge; } });
|
|
16
|
+
/**
|
|
17
|
+
* Formate une date ISO en format lisible français (wrapper avec time)
|
|
18
|
+
*/
|
|
19
|
+
function formatDate(isoString) {
|
|
20
|
+
const date = new Date(isoString);
|
|
21
|
+
// Utilise formatDateShared avec format 'long-time' (custom wrapper)
|
|
22
|
+
return date.toLocaleString('fr-FR', {
|
|
23
|
+
year: 'numeric',
|
|
24
|
+
month: 'long',
|
|
25
|
+
day: 'numeric',
|
|
26
|
+
hour: '2-digit',
|
|
27
|
+
minute: '2-digit'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Calcule et formate le temps total pour une liste de targets
|
|
32
|
+
*/
|
|
33
|
+
function calculateTotalTime(targets) {
|
|
34
|
+
const totalMinutes = targets.reduce((sum, t) => sum + t.totalTime.minutes, 0);
|
|
35
|
+
return (0, shared_1.formatTime)(totalMinutes, Math.round((totalMinutes / 60) * 100) / 100, Math.round((totalMinutes / 60 / 7) * 100) / 100);
|
|
36
|
+
}
|