@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,444 @@
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.scanDirectoryWithAST = scanDirectoryWithAST;
37
+ exports.logDetectionStats = logDetectionStats;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const ts_morph_1 = require("ts-morph");
41
+ const file_helpers_1 = require("../../utils/core/file-helpers");
42
+ const logger_1 = require("../../utils/core/logger");
43
+ const matchers_1 = require("./matchers");
44
+ const context_matcher_1 = require("./matchers/ts/context-matcher");
45
+ const scanner_regex_1 = require("../scanner-regex");
46
+ const scan_reporter_1 = require("../scan-reporter");
47
+ /**
48
+ * Groupe les règles AST par SyntaxKind pour optimisation
49
+ * Permet de ne tester que les règles pertinentes pour chaque type de nœud
50
+ */
51
+ function groupRulesByNodeType(rules) {
52
+ const grouped = new Map();
53
+ for (const rule of rules) {
54
+ if (!rule.astPattern)
55
+ continue;
56
+ const nodeTypes = Array.isArray(rule.astPattern.nodeType)
57
+ ? rule.astPattern.nodeType
58
+ : [rule.astPattern.nodeType];
59
+ for (const typeName of nodeTypes) {
60
+ if (!typeName)
61
+ continue;
62
+ const kind = ts_morph_1.SyntaxKind[typeName];
63
+ if (kind !== undefined) {
64
+ if (!grouped.has(kind)) {
65
+ grouped.set(kind, []);
66
+ }
67
+ grouped.get(kind).push(rule);
68
+ }
69
+ }
70
+ }
71
+ logger_1.logger.debug(`Règles groupées en ${grouped.size} types de nœuds`);
72
+ return grouped;
73
+ }
74
+ /**
75
+ * Scan un batch de fichiers avec les règles groupées par nodeType
76
+ * OPTIMISÉ : stratégie hybride selon nombre de types
77
+ */
78
+ function scanBatch(project, rulesByNodeType, projectRoot, matches) {
79
+ const kindCount = rulesByNodeType.size;
80
+ for (const sourceFile of project.getSourceFiles()) {
81
+ const filePath = sourceFile.getFilePath();
82
+ const relativeFilePath = path.relative(projectRoot, filePath);
83
+ // Stratégie adaptative selon nombre de types
84
+ if (kindCount <= 10) {
85
+ // Peu de types : getDescendantsOfKind() est optimal
86
+ scanWithGetDescendants(sourceFile, relativeFilePath, rulesByNodeType, matches);
87
+ }
88
+ else {
89
+ // Beaucoup de types : un seul parcours est plus rapide
90
+ scanWithSingleTraversal(sourceFile, relativeFilePath, rulesByNodeType, matches);
91
+ }
92
+ // IMPORTANT: Libérer mémoire des nœuds
93
+ sourceFile.forgetDescendants();
94
+ }
95
+ }
96
+ /**
97
+ * Scan avec getDescendantsOfKind() pour chaque type (optimal si peu de types)
98
+ */
99
+ function scanWithGetDescendants(sourceFile, relativeFilePath, rulesByNodeType, matches) {
100
+ for (const [kind, rulesForKind] of rulesByNodeType) {
101
+ const nodes = sourceFile.getDescendantsOfKind(kind);
102
+ for (const node of nodes) {
103
+ processNodeWithRules(node, rulesForKind, sourceFile, relativeFilePath, matches);
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * Scan avec un seul forEachDescendant() et dispatch (optimal si beaucoup de types)
109
+ */
110
+ function scanWithSingleTraversal(sourceFile, relativeFilePath, rulesByNodeType, matches) {
111
+ sourceFile.forEachDescendant((node) => {
112
+ const kind = node.getKind();
113
+ const rulesForKind = rulesByNodeType.get(kind);
114
+ if (rulesForKind) {
115
+ processNodeWithRules(node, rulesForKind, sourceFile, relativeFilePath, matches);
116
+ }
117
+ });
118
+ }
119
+ /**
120
+ * Traite un nœud avec les règles associées (logique commune)
121
+ */
122
+ function processNodeWithRules(node, rules, sourceFile, relativeFilePath, matches) {
123
+ for (const rule of rules) {
124
+ // Vérifier inFile filter (early exit)
125
+ if (rule.astPattern.inFile &&
126
+ !matchers_1.FileMatcher.matchesInFilePattern(relativeFilePath, rule.astPattern.inFile)) {
127
+ continue;
128
+ }
129
+ if ((0, matchers_1.matchesAstPattern)(node, rule.astPattern)) {
130
+ // Vérifier testSetup si présent
131
+ if (rule.astPattern.testSetup &&
132
+ !matchers_1.FileMatcher.matchesTestSetup(sourceFile, rule.astPattern.testSetup)) {
133
+ continue;
134
+ }
135
+ // Optimisation getText() : vérifier width d'abord
136
+ const width = node.getWidth();
137
+ const matchedText = width > 100
138
+ ? node.getText().substring(0, 100).trim()
139
+ : node.getText().trim();
140
+ matches.push({
141
+ ruleKey: rule.key,
142
+ ruleSummary: rule.summary,
143
+ filePath: relativeFilePath,
144
+ lineNumber: node.getStartLineNumber(),
145
+ matchedText
146
+ });
147
+ }
148
+ }
149
+ }
150
+ /**
151
+ * Scan un répertoire avec analyse AST TypeScript
152
+ * @param targetDir Répertoire à scanner (app ou lib)
153
+ * @param rules Règles avec astPattern défini
154
+ * @param projectRoot Racine du projet
155
+ * @returns Liste des occurrences trouvées et statistiques
156
+ */
157
+ function scanDirectoryWithAST(targetDir, rules, projectRoot) {
158
+ const matches = [];
159
+ const detectionMap = new Map();
160
+ // Séparer règles AST vs regex
161
+ // Note: regexRules inclut TOUTES les règles non-AST, y compris celles avec onFile (qui ont regex: null)
162
+ const astRules = rules.filter(r => r.astPattern);
163
+ const regexRules = rules.filter(r => !r.astPattern);
164
+ // Fallback vers scanner regex si pas d'AST
165
+ if (astRules.length === 0) {
166
+ logger_1.logger.debug('Aucune règle AST, utilisation du scanner regex');
167
+ const regexMatches = (0, scanner_regex_1.scanDirectory)(targetDir, regexRules, projectRoot);
168
+ // Tracker les stats regex
169
+ for (const match of regexMatches) {
170
+ const existing = detectionMap.get(match.ruleKey);
171
+ if (existing) {
172
+ existing.count++;
173
+ }
174
+ else {
175
+ detectionMap.set(match.ruleKey, { method: 'regex', count: 1 });
176
+ }
177
+ }
178
+ const stats = Array.from(detectionMap.entries()).map(([ruleKey, data]) => ({
179
+ ruleKey,
180
+ method: data.method,
181
+ occurrences: data.count
182
+ }));
183
+ return { matches: regexMatches, stats };
184
+ }
185
+ logger_1.logger.info(`Scanner: ${astRules.length} règles AST, ${regexRules.length} règles Regex (fallback)`);
186
+ // Grouper les règles par nodeType UNE SEULE FOIS
187
+ const rulesByNodeType = groupRulesByNodeType(astRules);
188
+ // Créer un projet ts-morph
189
+ const project = createTsMorphProject();
190
+ // Collecter tous les fichiers .ts récursivement
191
+ const tsFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.ts$/);
192
+ const totalFiles = tsFiles.length;
193
+ (0, scan_reporter_1.logFilesDetected)('TypeScript', totalFiles);
194
+ // Charger TOUS les fichiers dans le projet (pas de batch)
195
+ // Permet la résolution cross-file (referTo, getType(), findReferences())
196
+ logger_1.logger.info('Chargement de tous les fichiers TypeScript...');
197
+ addFilesToProject(project, tsFiles);
198
+ logger_1.logger.success(`${totalFiles} fichiers chargés en mémoire`);
199
+ // Scanner tous les fichiers en une seule passe
200
+ logger_1.logger.info('Analyse AST en cours...');
201
+ scanBatch(project, rulesByNodeType, projectRoot, matches);
202
+ logger_1.logger.success('Analyse AST terminée');
203
+ // Libérer la mémoire À LA FIN uniquement
204
+ logger_1.logger.debug('Libération mémoire...');
205
+ const sourceFiles = project.getSourceFiles();
206
+ for (const sourceFile of sourceFiles) {
207
+ project.removeSourceFile(sourceFile);
208
+ }
209
+ // Vider les caches
210
+ (0, context_matcher_1.clearContextCaches)();
211
+ (0, matchers_1.clearTemplateCache)();
212
+ // Scanner les fichiers HTML pour les patterns HTML
213
+ const htmlFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.html$/);
214
+ if (htmlFiles.length > 0) {
215
+ (0, scan_reporter_1.logFilesDetected)('HTML', htmlFiles.length);
216
+ }
217
+ scanHtmlFiles(targetDir, astRules, projectRoot, matches);
218
+ // Tracker les stats AST
219
+ for (const match of matches) {
220
+ const existing = detectionMap.get(match.ruleKey);
221
+ if (existing) {
222
+ existing.count++;
223
+ }
224
+ else {
225
+ detectionMap.set(match.ruleKey, { method: 'ast', count: 1 });
226
+ }
227
+ }
228
+ const astMatchCount = matches.length;
229
+ logger_1.logger.success(`AST: ${astMatchCount} occurrences`);
230
+ // Fallback: Scanner les règles sans astPattern avec Regex
231
+ if (regexRules.length > 0) {
232
+ const regexMatches = (0, scanner_regex_1.scanDirectory)(targetDir, regexRules, projectRoot);
233
+ // Tracker les stats Regex
234
+ for (const match of regexMatches) {
235
+ const existing = detectionMap.get(match.ruleKey);
236
+ if (existing) {
237
+ existing.count++;
238
+ }
239
+ else {
240
+ detectionMap.set(match.ruleKey, { method: 'regex', count: 1 });
241
+ }
242
+ }
243
+ matches.push(...regexMatches);
244
+ logger_1.logger.success(`Regex (fallback): ${regexMatches.length} occurrences`);
245
+ }
246
+ // Convertir la map en array de stats
247
+ const stats = Array.from(detectionMap.entries()).map(([ruleKey, data]) => ({
248
+ ruleKey,
249
+ method: data.method,
250
+ occurrences: data.count
251
+ }));
252
+ return { matches, stats };
253
+ }
254
+ /**
255
+ * Affiche un rapport détaillé des statistiques de détection
256
+ */
257
+ function logDetectionStats(stats) {
258
+ logger_1.logger.blank();
259
+ logger_1.logger.info('═══════════════════════════════════════════════════════════');
260
+ logger_1.logger.info(' RAPPORT DE DÉTECTION PAR MÉTHODE');
261
+ logger_1.logger.info('═══════════════════════════════════════════════════════════');
262
+ logger_1.logger.blank();
263
+ // Séparer AST et Regex
264
+ const astStats = stats.filter(s => s.method === 'ast').sort((a, b) => b.occurrences - a.occurrences);
265
+ const regexStats = stats.filter(s => s.method === 'regex').sort((a, b) => b.occurrences - a.occurrences);
266
+ const totalAstOccurrences = astStats.reduce((sum, s) => sum + s.occurrences, 0);
267
+ const totalRegexOccurrences = regexStats.reduce((sum, s) => sum + s.occurrences, 0);
268
+ const totalOccurrences = totalAstOccurrences + totalRegexOccurrences;
269
+ // Résumé global
270
+ logger_1.logger.info(`Total occurrences: ${totalOccurrences}`);
271
+ logger_1.logger.info(` • AST: ${totalAstOccurrences} (${getPercentage(totalAstOccurrences, totalOccurrences)}%)`);
272
+ logger_1.logger.info(` • Regex: ${totalRegexOccurrences} (${getPercentage(totalRegexOccurrences, totalOccurrences)}%)`);
273
+ logger_1.logger.blank();
274
+ logger_1.logger.info(`Règles actives: ${stats.length}`);
275
+ logger_1.logger.info(` • Avec AST: ${astStats.length}`);
276
+ logger_1.logger.info(` • Regex fallback: ${regexStats.length}`);
277
+ logger_1.logger.blank();
278
+ // Détail règles AST
279
+ if (astStats.length > 0) {
280
+ logger_1.logger.info('Règles détectées par AST:');
281
+ for (const stat of astStats) {
282
+ logger_1.logger.debug(` ✓ ${stat.ruleKey}: ${stat.occurrences} occurrences`);
283
+ }
284
+ logger_1.logger.blank();
285
+ }
286
+ // Détail règles Regex (fallback)
287
+ if (regexStats.length > 0) {
288
+ logger_1.logger.info('Règles détectées par Regex (fallback):');
289
+ for (const stat of regexStats) {
290
+ logger_1.logger.debug(` ⚠ ${stat.ruleKey}: ${stat.occurrences} occurrences`);
291
+ }
292
+ logger_1.logger.blank();
293
+ }
294
+ logger_1.logger.info('═══════════════════════════════════════════════════════════');
295
+ logger_1.logger.blank();
296
+ }
297
+ /**
298
+ * Calcule un pourcentage arrondi
299
+ */
300
+ function getPercentage(value, total) {
301
+ if (total === 0)
302
+ return '0.0';
303
+ return ((value / total) * 100).toFixed(1);
304
+ }
305
+ /**
306
+ * Scan les fichiers HTML avec les patterns HTML (Attribute, PipeExpression, Element, etc.)
307
+ * Scanne à la fois :
308
+ * - Les fichiers .html externes (95% des cas en Angular)
309
+ * - Les templates inline dans les .ts (5% des cas, pour patterns standalone)
310
+ *
311
+ * Note: Les patterns Decorator avec template/templateUrl sont déjà gérés
312
+ * par le DecoratorMatcher lors du scan TypeScript principal.
313
+ */
314
+ function scanHtmlFiles(targetDir, rules, projectRoot, matches) {
315
+ // Filtrer uniquement les règles HTML
316
+ const htmlRules = rules.filter(r => r.astPattern && matchers_1.HtmlMatcher.isHtmlPattern(r.astPattern));
317
+ if (htmlRules.length === 0) {
318
+ return;
319
+ }
320
+ // 1. Scanner les fichiers .html externes (cas le plus courant)
321
+ const htmlFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.html$/);
322
+ for (const htmlFile of htmlFiles) {
323
+ const relativeFilePath = path.relative(projectRoot, htmlFile);
324
+ try {
325
+ const htmlContent = fs.readFileSync(htmlFile, 'utf-8');
326
+ for (const rule of htmlRules) {
327
+ const htmlMatches = matchers_1.HtmlMatcher.findHtmlPatternMatches(htmlContent, rule.astPattern);
328
+ for (const match of htmlMatches) {
329
+ matches.push({
330
+ ruleKey: rule.key,
331
+ ruleSummary: rule.summary,
332
+ filePath: relativeFilePath,
333
+ lineNumber: match.lineNumber,
334
+ matchedText: match.matchedText
335
+ });
336
+ }
337
+ }
338
+ }
339
+ catch (error) {
340
+ logger_1.logger.warning(`Impossible de lire le fichier HTML: ${htmlFile}`);
341
+ }
342
+ }
343
+ // 2. Scanner les templates inline dans les fichiers .ts (moins courant mais nécessaire)
344
+ scanInlineTemplates(targetDir, htmlRules, projectRoot, matches);
345
+ }
346
+ /**
347
+ * Scan les templates inline dans les fichiers TypeScript
348
+ * Détecte les patterns HTML standalone dans les templates définis avec template: `...`
349
+ *
350
+ * Note: Les patterns Decorator (avec contexte @Component) sont déjà gérés par DecoratorMatcher.
351
+ * Cette fonction gère uniquement les patterns HTML sans contexte Decorator (Element, Attribute, etc.)
352
+ */
353
+ function scanInlineTemplates(targetDir, htmlRules, projectRoot, matches) {
354
+ const tsFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.ts$/);
355
+ for (const tsFile of tsFiles) {
356
+ try {
357
+ const fileContent = fs.readFileSync(tsFile, 'utf-8');
358
+ const relativeFilePath = path.relative(projectRoot, tsFile);
359
+ // Regex pour extraire templates inline (supporte backticks simples)
360
+ // Capture: template: `...` avec support multiline
361
+ const templateRegex = /template\s*:\s*`([^`]*)`/gs;
362
+ let match;
363
+ while ((match = templateRegex.exec(fileContent)) !== null) {
364
+ const htmlContent = match[1];
365
+ const templateStartIndex = match.index + match[0].indexOf(match[1]);
366
+ // Calculer la ligne de début du template dans le fichier .ts
367
+ const linesBefore = fileContent.substring(0, templateStartIndex).split('\n').length;
368
+ for (const rule of htmlRules) {
369
+ const htmlMatches = matchers_1.HtmlMatcher.findHtmlPatternMatches(htmlContent, rule.astPattern);
370
+ for (const htmlMatch of htmlMatches) {
371
+ // Ajuster le numéro de ligne pour pointer vers le fichier .ts
372
+ const actualLineNumber = linesBefore + htmlMatch.lineNumber - 1;
373
+ matches.push({
374
+ ruleKey: rule.key,
375
+ ruleSummary: rule.summary,
376
+ filePath: relativeFilePath,
377
+ lineNumber: actualLineNumber,
378
+ matchedText: htmlMatch.matchedText
379
+ });
380
+ }
381
+ }
382
+ }
383
+ }
384
+ catch (error) {
385
+ // Silencieux - pas critique si on ne peut pas lire un fichier .ts
386
+ // (le scan TypeScript principal l'aurait déjà signalé si vraiment problématique)
387
+ }
388
+ }
389
+ }
390
+ /**
391
+ * Crée un projet ts-morph avec configuration optimale
392
+ */
393
+ function createTsMorphProject() {
394
+ return new ts_morph_1.Project({
395
+ skipAddingFilesFromTsConfig: true,
396
+ compilerOptions: {
397
+ target: 99, // ESNext
398
+ module: 99, // ESNext
399
+ }
400
+ });
401
+ }
402
+ /**
403
+ * Ajoute les fichiers au projet ts-morph avec gestion d'erreurs
404
+ */
405
+ function addFilesToProject(project, tsFiles) {
406
+ for (const filePath of tsFiles) {
407
+ try {
408
+ project.addSourceFileAtPath(filePath);
409
+ }
410
+ catch (error) {
411
+ logger_1.logger.warning(`Impossible d'analyser: ${filePath}`);
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * Trouve les matches d'un pattern AST dans un fichier
417
+ */
418
+ function findAstPatternMatches(sourceFile, rule, relativeFilePath) {
419
+ const matches = [];
420
+ const pattern = rule.astPattern;
421
+ // Vérifier le filtre inFile (filtrage par nom de fichier)
422
+ if (pattern.inFile && !matchers_1.FileMatcher.matchesInFilePattern(relativeFilePath, pattern.inFile)) {
423
+ return matches;
424
+ }
425
+ // Parcourir tous les nœuds de l'AST
426
+ sourceFile.forEachDescendant((node) => {
427
+ if ((0, matchers_1.matchesAstPattern)(node, pattern)) {
428
+ // Vérifier testSetup si présent dans le pattern
429
+ if (pattern.testSetup && !matchers_1.FileMatcher.matchesTestSetup(sourceFile, pattern.testSetup)) {
430
+ return; // Skip ce nœud si testSetup ne correspond pas
431
+ }
432
+ const lineNumber = node.getStartLineNumber();
433
+ const matchedText = node.getText().substring(0, 100); // Limiter à 100 caractères
434
+ matches.push({
435
+ ruleKey: rule.key,
436
+ ruleSummary: rule.summary,
437
+ filePath: relativeFilePath,
438
+ lineNumber,
439
+ matchedText: matchedText.trim()
440
+ });
441
+ }
442
+ });
443
+ return matches;
444
+ }
@@ -0,0 +1,196 @@
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.detectProject = detectProject;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const app_analyzer_1 = require("./app-analyzer");
40
+ /**
41
+ * Détecte le type de projet Angular (Nx Monorepo ou Standalone)
42
+ */
43
+ function detectProject(projectPath) {
44
+ const nxJsonPath = path.join(projectPath, 'nx.json');
45
+ const angularJsonPath = path.join(projectPath, 'angular.json');
46
+ const packageJsonPath = path.join(projectPath, 'package.json');
47
+ const appsDir = path.join(projectPath, 'apps');
48
+ const libsDir = path.join(projectPath, 'libs');
49
+ if (!fs.existsSync(projectPath)) {
50
+ throw new Error(`Le chemin du projet n'existe pas: ${projectPath}`);
51
+ }
52
+ const hasNxJson = fs.existsSync(nxJsonPath);
53
+ const hasAngularJson = fs.existsSync(angularJsonPath);
54
+ const hasPackageJson = fs.existsSync(packageJsonPath);
55
+ const hasAppsDir = fs.existsSync(appsDir);
56
+ const hasLibsDir = fs.existsSync(libsDir);
57
+ let type = 'unknown';
58
+ let name = path.basename(projectPath);
59
+ let angularVersion;
60
+ let appNames = [];
61
+ let libNames = [];
62
+ if (hasNxJson && hasAppsDir) {
63
+ type = 'nx-monorepo';
64
+ appNames = extractNxApps(projectPath);
65
+ if (hasLibsDir) {
66
+ libNames = extractNxLibs(projectPath);
67
+ }
68
+ }
69
+ else if (hasAngularJson) {
70
+ type = 'angular-standalone';
71
+ appNames = extractAngularApps(angularJsonPath);
72
+ }
73
+ if (hasPackageJson) {
74
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
75
+ name = packageJson.name || name;
76
+ angularVersion = extractAngularVersion(packageJson);
77
+ }
78
+ const isNxMonorepo = type === 'nx-monorepo';
79
+ const apps = appNames.map((appName) => (0, app_analyzer_1.analyzeApp)(appName, projectPath, isNxMonorepo));
80
+ const libs = isNxMonorepo
81
+ ? libNames.map((libName) => (0, app_analyzer_1.analyzeLib)(libName, projectPath))
82
+ : [];
83
+ return {
84
+ path: projectPath,
85
+ type,
86
+ name,
87
+ angularVersion,
88
+ apps,
89
+ libs
90
+ };
91
+ }
92
+ function extractNxApps(projectPath) {
93
+ const appsDir = path.join(projectPath, 'apps');
94
+ if (!fs.existsSync(appsDir)) {
95
+ return [];
96
+ }
97
+ try {
98
+ const entries = fs.readdirSync(appsDir, { withFileTypes: true });
99
+ return entries
100
+ .filter((entry) => entry.isDirectory())
101
+ .filter((dir) => {
102
+ const projectJsonPath = path.join(appsDir, dir.name, 'project.json');
103
+ return fs.existsSync(projectJsonPath);
104
+ })
105
+ .map((dir) => dir.name);
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ function extractAngularApps(angularJsonPath) {
112
+ if (!fs.existsSync(angularJsonPath)) {
113
+ return [];
114
+ }
115
+ try {
116
+ const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf-8'));
117
+ return Object.keys(angularJson.projects || {}).filter((projectName) => {
118
+ const project = angularJson.projects[projectName];
119
+ return project.projectType === 'application';
120
+ });
121
+ }
122
+ catch {
123
+ return [];
124
+ }
125
+ }
126
+ function extractNxLibs(projectPath) {
127
+ const libsDir = path.join(projectPath, 'libs');
128
+ if (!fs.existsSync(libsDir)) {
129
+ return [];
130
+ }
131
+ try {
132
+ const libs = [];
133
+ // Fonction récursive pour scanner les libs (support structure groupée)
134
+ function scanLibsRecursive(dir, relativePath = '') {
135
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (!entry.isDirectory())
138
+ continue;
139
+ const fullPath = path.join(dir, entry.name);
140
+ const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
141
+ if (isValidNxLib(fullPath)) {
142
+ // C'est une lib valide Nx
143
+ libs.push(newRelativePath);
144
+ }
145
+ else {
146
+ // C'est un dossier de groupe, scanner récursivement
147
+ scanLibsRecursive(fullPath, newRelativePath);
148
+ }
149
+ }
150
+ }
151
+ scanLibsRecursive(libsDir);
152
+ return libs;
153
+ }
154
+ catch {
155
+ return [];
156
+ }
157
+ }
158
+ /**
159
+ * Vérifie si un dossier est une lib Nx valide
160
+ * Critères obligatoires :
161
+ * 1. project.json (configuration Nx)
162
+ * 2. index.ts (racine OU src/) OU src/lib/ (code de la lib)
163
+ * 3. tsconfig.json ou tsconfig.lib.json (configuration TypeScript)
164
+ */
165
+ function isValidNxLib(libPath) {
166
+ // 1. project.json obligatoire
167
+ const projectJsonPath = path.join(libPath, 'project.json');
168
+ if (!fs.existsSync(projectJsonPath)) {
169
+ return false;
170
+ }
171
+ // 2. Vérifier présence de code source
172
+ // Option A : index.ts à la racine (ex: libs/apis/deployment-tool/index.ts)
173
+ const hasRootIndexTs = fs.existsSync(path.join(libPath, 'index.ts'));
174
+ // Option B : src/index.ts (structure standard)
175
+ const hasSrcIndexTs = fs.existsSync(path.join(libPath, 'src', 'index.ts'));
176
+ // Option C : src/lib/ (barrel export dans lib/)
177
+ const hasSrcLibDir = fs.existsSync(path.join(libPath, 'src', 'lib'));
178
+ if (!hasRootIndexTs && !hasSrcIndexTs && !hasSrcLibDir) {
179
+ return false;
180
+ }
181
+ return true;
182
+ }
183
+ function extractAngularVersion(packageJson) {
184
+ if (typeof packageJson !== 'object' ||
185
+ packageJson === null ||
186
+ !('dependencies' in packageJson)) {
187
+ return undefined;
188
+ }
189
+ const deps = packageJson.dependencies;
190
+ const angularCore = deps['@angular/core'];
191
+ if (!angularCore) {
192
+ return undefined;
193
+ }
194
+ const versionMatch = angularCore.match(/(\d+)\./);
195
+ return versionMatch ? versionMatch[1] : undefined;
196
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProjectStrategy = exports.StandaloneStrategy = exports.NxStrategy = void 0;
4
+ var nx_strategy_1 = require("./nx-strategy");
5
+ Object.defineProperty(exports, "NxStrategy", { enumerable: true, get: function () { return nx_strategy_1.NxStrategy; } });
6
+ var standalone_strategy_1 = require("./standalone-strategy");
7
+ Object.defineProperty(exports, "StandaloneStrategy", { enumerable: true, get: function () { return standalone_strategy_1.StandaloneStrategy; } });
8
+ var strategy_factory_1 = require("./strategy-factory");
9
+ Object.defineProperty(exports, "createProjectStrategy", { enumerable: true, get: function () { return strategy_factory_1.createProjectStrategy; } });