@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.
Files changed (122) hide show
  1. package/LICENSE +96 -0
  2. package/README.fr.md +359 -0
  3. package/README.md +360 -0
  4. package/SECURITY.md +187 -0
  5. package/dist/client.bundle.js +357 -0
  6. package/dist/src/core/app-analyzer.js +134 -0
  7. package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
  8. package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
  9. package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
  10. package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
  11. package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
  12. package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
  13. package/dist/src/core/ast/matchers/html/index.js +118 -0
  14. package/dist/src/core/ast/matchers/index.js +377 -0
  15. package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
  16. package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
  17. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
  18. package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
  19. package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
  20. package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
  21. package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
  22. package/dist/src/core/ast/matchers/ts/index.js +53 -0
  23. package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
  24. package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
  25. package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
  26. package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
  27. package/dist/src/core/ast/scanner-ast.js +444 -0
  28. package/dist/src/core/project-detector.js +196 -0
  29. package/dist/src/core/project-strategy/index.js +9 -0
  30. package/dist/src/core/project-strategy/nx-strategy.js +130 -0
  31. package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
  32. package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
  33. package/dist/src/core/project-strategy/strategy-factory.js +15 -0
  34. package/dist/src/core/rules-loader.js +89 -0
  35. package/dist/src/core/scan-reporter.js +316 -0
  36. package/dist/src/core/scanner-delta.js +339 -0
  37. package/dist/src/core/scanner-orchestrator.js +266 -0
  38. package/dist/src/core/scanner-regex.js +298 -0
  39. package/dist/src/core/workload/calculator.js +82 -0
  40. package/dist/src/core/workload/constants.js +15 -0
  41. package/dist/src/core/workload/grouping.js +18 -0
  42. package/dist/src/core/workload/hierarchy-calculator.js +127 -0
  43. package/dist/src/core/workload/index.js +11 -0
  44. package/dist/src/core/workload/metadata.js +20 -0
  45. package/dist/src/core/workload/special-workload.js +101 -0
  46. package/dist/src/core/workload/target-resolver.js +34 -0
  47. package/dist/src/data/angular-migration-rules.json +2337 -0
  48. package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
  49. package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
  50. package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
  51. package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
  52. package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
  53. package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
  54. package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
  55. package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
  56. package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
  57. package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
  58. package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
  59. package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
  60. package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
  61. package/dist/src/index.js +161 -0
  62. package/dist/src/models/chip-config.js +45 -0
  63. package/dist/src/models/interfaces/app-details.interface.js +2 -0
  64. package/dist/src/models/interfaces/ast-interfaces.js +5 -0
  65. package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
  66. package/dist/src/models/interfaces/client-interfaces.js +6 -0
  67. package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
  68. package/dist/src/models/interfaces/html-match.interface.js +2 -0
  69. package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
  70. package/dist/src/models/interfaces/lib-details.interface.js +2 -0
  71. package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
  72. package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
  73. package/dist/src/models/interfaces/project-info.interface.js +2 -0
  74. package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
  75. package/dist/src/models/interfaces/rule-match.interface.js +2 -0
  76. package/dist/src/models/interfaces/rule.interface.js +2 -0
  77. package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
  78. package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
  79. package/dist/src/models/interfaces/special-workload.interface.js +2 -0
  80. package/dist/src/models/interfaces/workload-report.interface.js +2 -0
  81. package/dist/src/models/types/build-block-blob.type.js +2 -0
  82. package/dist/src/models/types/migration-version.type.js +2 -0
  83. package/dist/src/models/types/project-type.type.js +2 -0
  84. package/dist/src/models/types/risk-level.type.js +2 -0
  85. package/dist/src/models/types/rule-category.type.js +2 -0
  86. package/dist/src/models/types/rule-priority.type.js +2 -0
  87. package/dist/src/models/types/rule-workload-type.type.js +2 -0
  88. package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
  89. package/dist/src/templates/landing/card-app-info.template.js +63 -0
  90. package/dist/src/templates/landing/card-lib-info.template.js +67 -0
  91. package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
  92. package/dist/src/templates/landing/nx-summary.template.js +115 -0
  93. package/dist/src/templates/landing/project-overview.template.js +27 -0
  94. package/dist/src/templates/page/index-page.template.js +95 -0
  95. package/dist/src/templates/page/main.template.js +83 -0
  96. package/dist/src/templates/page/migration-guide.template.js +175 -0
  97. package/dist/src/templates/page/workload-report.template.js +53 -0
  98. package/dist/src/templates/workload/dashboard.template.js +184 -0
  99. package/dist/src/templates/workload/filters-panel.template.js +215 -0
  100. package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
  101. package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
  102. package/dist/src/templates/workload/hierarchy-shared.js +163 -0
  103. package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
  104. package/dist/src/templates/workload/hierarchy.template.js +35 -0
  105. package/dist/src/templates/workload/rule-modal.template.js +280 -0
  106. package/dist/src/utils/core/args-parser.js +123 -0
  107. package/dist/src/utils/core/array-helpers.js +18 -0
  108. package/dist/src/utils/core/ast-helpers.js +99 -0
  109. package/dist/src/utils/core/file-helpers.js +109 -0
  110. package/dist/src/utils/core/html-helpers.js +36 -0
  111. package/dist/src/utils/core/index.js +28 -0
  112. package/dist/src/utils/core/logger.js +38 -0
  113. package/dist/src/utils/core/rule-helpers.js +15 -0
  114. package/dist/src/utils/core/workload-formatter.js +6 -0
  115. package/dist/src/utils/shared/array-helpers.js +25 -0
  116. package/dist/src/utils/shared/date-helpers.js +109 -0
  117. package/dist/src/utils/shared/html-helpers.js +37 -0
  118. package/dist/src/utils/shared/index.js +25 -0
  119. package/dist/src/utils/shared/rule-helpers.js +20 -0
  120. package/dist/src/utils/shared/time-formatters.js +76 -0
  121. package/dist/styles.css +2 -0
  122. 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
+ }