@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,298 @@
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.scanDirectory = scanDirectory;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const core_1 = require("../utils/core");
41
+ const core_2 = require("../utils/core");
42
+ /**
43
+ * Vérifie si le projet est un monorepo Nx
44
+ * @param projectRoot Racine du projet
45
+ * @returns true si le projet a un fichier nx.json
46
+ */
47
+ function isNxProject(projectRoot) {
48
+ const nxJsonPath = path.join(projectRoot, 'nx.json');
49
+ return fs.existsSync(nxJsonPath);
50
+ }
51
+ /**
52
+ * Exécute une commande système et retourne sa sortie
53
+ * @param command Commande à exécuter (ex: "node -v")
54
+ * @returns Sortie de la commande ou null en cas d'erreur
55
+ */
56
+ function executeCommand(command) {
57
+ try {
58
+ const output = (0, child_process_1.execSync)(command, {
59
+ encoding: 'utf-8',
60
+ stdio: ['pipe', 'pipe', 'pipe']
61
+ });
62
+ return output.trim();
63
+ }
64
+ catch (error) {
65
+ core_2.logger.warning(`Échec exécution commande: ${command}`);
66
+ return null;
67
+ }
68
+ }
69
+ /**
70
+ * Scan les règles de routine basées sur commande système (routineCmd)
71
+ * @param rules Règles avec routineCmd défini
72
+ * @param projectRoot Racine du projet (pour chemins relatifs)
73
+ * @returns Liste des matches trouvés
74
+ */
75
+ function scanRoutineCommands(rules, projectRoot) {
76
+ const matches = [];
77
+ for (const rule of rules) {
78
+ if (!rule.routineCmd || !rule.regex) {
79
+ continue;
80
+ }
81
+ const commandOutput = executeCommand(rule.routineCmd);
82
+ if (commandOutput === null) {
83
+ continue;
84
+ }
85
+ // Tester la sortie avec la regex de la règle
86
+ const compiledRegex = new RegExp(rule.regex, 'g');
87
+ const match = compiledRegex.exec(commandOutput);
88
+ if (match) {
89
+ // Match trouvé = règle pertinente (action nécessaire)
90
+ matches.push({
91
+ ruleKey: rule.key,
92
+ ruleSummary: rule.summary,
93
+ filePath: '<system>',
94
+ lineNumber: 1,
95
+ matchedText: commandOutput
96
+ });
97
+ }
98
+ }
99
+ return matches;
100
+ }
101
+ /**
102
+ * Scan un répertoire et applique les règles de migration
103
+ * @param targetDir Répertoire à scanner (app ou lib)
104
+ * @param rules Règles de migration à appliquer
105
+ * @param projectRoot Racine du projet (pour chemins relatifs)
106
+ * @returns Liste des occurrences trouvées
107
+ */
108
+ function scanDirectory(targetDir, rules, projectRoot) {
109
+ const matches = [];
110
+ // Scanner les fichiers spécifiques (onFile: ex. package.json)
111
+ // PRIORITÉ 1 : Chercher onFile à la racine du projet
112
+ // PRIORITÉ 2 : Si pas trouvé ET onFileNx existe ET projet Nx → Chercher onFileNx
113
+ // PRIORITÉ 3 : Fallback targetDir (fichiers custom par app/lib)
114
+ for (const rule of rules.filter(r => r.onFile)) {
115
+ let targetFile = null;
116
+ // 1. Chercher onFile à la racine du projet
117
+ const rootFilePath = path.join(projectRoot, rule.onFile);
118
+ if (fs.existsSync(rootFilePath)) {
119
+ targetFile = rootFilePath;
120
+ }
121
+ // 2. Si pas trouvé ET onFileNx existe ET projet Nx → Chercher onFileNx
122
+ else if (rule.onFileNx && isNxProject(projectRoot)) {
123
+ const nxFilePath = path.join(projectRoot, rule.onFileNx);
124
+ if (fs.existsSync(nxFilePath)) {
125
+ targetFile = nxFilePath;
126
+ }
127
+ }
128
+ // 3. Fallback : chercher dans targetDir (pour fichiers spécifiques à l'app/lib)
129
+ if (!targetFile) {
130
+ const targetFilePath = path.join(targetDir, rule.onFile);
131
+ if (fs.existsSync(targetFilePath)) {
132
+ targetFile = targetFilePath;
133
+ }
134
+ }
135
+ // Scanner le fichier trouvé si règle a une regex
136
+ if (targetFile && rule.regex) {
137
+ matches.push(...applyRule(targetFile, rule, projectRoot));
138
+ }
139
+ }
140
+ // Grouper les règles par fileType pattern (ex. *.ts → [règle1, règle2])
141
+ const fileTypePatternToRules = new Map();
142
+ const compiledFileTypePatterns = new Map();
143
+ for (const rule of rules.filter(r => !r.onFile)) {
144
+ for (const fileTypePattern of rule.fileTypes) {
145
+ // Grouper les règles
146
+ if (!fileTypePatternToRules.has(fileTypePattern)) {
147
+ fileTypePatternToRules.set(fileTypePattern, []);
148
+ // Compiler le pattern une seule fois
149
+ // IMPORTANT: Échapper les points AVANT de convertir les étoiles
150
+ const regexPattern = '^' + fileTypePattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
151
+ compiledFileTypePatterns.set(fileTypePattern, new RegExp(regexPattern));
152
+ }
153
+ fileTypePatternToRules.get(fileTypePattern).push(rule);
154
+ }
155
+ }
156
+ // Scanner récursivement uniquement les fichiers qui matchent
157
+ if (fileTypePatternToRules.size > 0) {
158
+ scanRecursively(targetDir, fileTypePatternToRules, compiledFileTypePatterns, projectRoot, matches);
159
+ }
160
+ return matches;
161
+ }
162
+ /**
163
+ * Scan récursif des fichiers selon les patterns
164
+ * @param currentDir Répertoire en cours de scan
165
+ * @param fileTypePatternToRules Map des fileType patterns vers leurs règles
166
+ * @param compiledFileTypePatterns Map des patterns vers leurs regex compilées
167
+ * @param projectRoot Racine du projet
168
+ * @param matches Accumulateur de résultats (mutated)
169
+ */
170
+ function scanRecursively(currentDir, fileTypePatternToRules, compiledFileTypePatterns, projectRoot, matches) {
171
+ try {
172
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
173
+ for (const entry of entries) {
174
+ const fullPath = path.join(currentDir, entry.name);
175
+ if (entry.isDirectory()) {
176
+ if (!(0, core_1.shouldIgnoreDir)(entry.name)) {
177
+ scanRecursively(fullPath, fileTypePatternToRules, compiledFileTypePatterns, projectRoot, matches);
178
+ }
179
+ }
180
+ else if (entry.isFile()) {
181
+ // Trouver les règles applicables pour ce fichier (dédupliquées)
182
+ const applicableRules = new Set();
183
+ for (const [fileTypePattern, rulesForPattern] of fileTypePatternToRules) {
184
+ const compiledPattern = compiledFileTypePatterns.get(fileTypePattern);
185
+ if (compiledPattern.test(entry.name)) {
186
+ rulesForPattern.forEach(r => applicableRules.add(r));
187
+ }
188
+ }
189
+ // Scanner uniquement si au moins une règle s'applique
190
+ if (applicableRules.size > 0) {
191
+ matches.push(...applyRules(fullPath, Array.from(applicableRules), projectRoot));
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch (error) {
197
+ const errorMsg = error instanceof Error ? error.message : String(error);
198
+ core_2.logger.warning(`Impossible de lire: ${currentDir} - ${errorMsg}`);
199
+ }
200
+ }
201
+ /**
202
+ * Détecte si une regex nécessite un contexte multiligne
203
+ * Patterns détectés : [\s\S], [\S\s], \n, \r
204
+ */
205
+ function needsMultilineContext(regexPattern) {
206
+ return regexPattern.includes('[\\s\\S]') ||
207
+ regexPattern.includes('[\\S\\s]') ||
208
+ regexPattern.includes('\\n') ||
209
+ regexPattern.includes('\\r');
210
+ }
211
+ /**
212
+ * Trouve le numéro de ligne d'un match dans le contenu
213
+ */
214
+ function findLineNumber(content, matchIndex) {
215
+ const textBeforeMatch = content.substring(0, matchIndex);
216
+ return textBeforeMatch.split('\n').length;
217
+ }
218
+ /**
219
+ * Applique plusieurs règles à un fichier (une seule lecture)
220
+ * @param filePath Chemin complet du fichier
221
+ * @param rules Règles de migration à appliquer
222
+ * @param projectRoot Racine du projet (pour chemin relatif)
223
+ */
224
+ function applyRules(filePath, rules, projectRoot) {
225
+ const matches = [];
226
+ // Séparer les règles selon leur type (ligne unique vs multiligne)
227
+ const singleLineRules = [];
228
+ const multilineRules = [];
229
+ for (const rule of rules) {
230
+ if (rule.regex) {
231
+ const compiledRegex = new RegExp(rule.regex, 'g');
232
+ const ruleWithRegex = { rule, regex: compiledRegex };
233
+ if (needsMultilineContext(rule.regex)) {
234
+ multilineRules.push(ruleWithRegex);
235
+ }
236
+ else {
237
+ singleLineRules.push(ruleWithRegex);
238
+ }
239
+ }
240
+ }
241
+ if (singleLineRules.length === 0 && multilineRules.length === 0) {
242
+ return matches;
243
+ }
244
+ try {
245
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
246
+ const relativeFilePath = path.relative(projectRoot, filePath);
247
+ // ÉTAPE 1 : Appliquer les règles multilignes sur le fichier entier
248
+ for (const { rule, regex } of multilineRules) {
249
+ regex.lastIndex = 0;
250
+ let match;
251
+ while ((match = regex.exec(fileContent)) !== null) {
252
+ const lineNumber = findLineNumber(fileContent, match.index);
253
+ matches.push({
254
+ ruleKey: rule.key,
255
+ ruleSummary: rule.summary,
256
+ filePath: relativeFilePath,
257
+ lineNumber,
258
+ matchedText: match[0].trim()
259
+ });
260
+ }
261
+ }
262
+ // ÉTAPE 2 : Appliquer les règles simples ligne par ligne
263
+ if (singleLineRules.length > 0) {
264
+ const fileLines = fileContent.split('\n');
265
+ for (let lineIndex = 0; lineIndex < fileLines.length; lineIndex++) {
266
+ const currentLine = fileLines[lineIndex];
267
+ for (const { rule, regex } of singleLineRules) {
268
+ regex.lastIndex = 0;
269
+ const lineMatches = currentLine.match(regex);
270
+ if (lineMatches) {
271
+ for (const match of lineMatches) {
272
+ matches.push({
273
+ ruleKey: rule.key,
274
+ ruleSummary: rule.summary,
275
+ filePath: relativeFilePath,
276
+ lineNumber: lineIndex + 1,
277
+ matchedText: match.trim()
278
+ });
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ catch (error) {
286
+ core_2.logger.warning(`Impossible de lire: ${filePath}`);
287
+ }
288
+ return matches;
289
+ }
290
+ /**
291
+ * Applique une règle regex à un fichier (pour onFile)
292
+ * @param filePath Chemin complet du fichier
293
+ * @param rule Règle de migration à appliquer
294
+ * @param projectRoot Racine du projet (pour chemin relatif)
295
+ */
296
+ function applyRule(filePath, rule, projectRoot) {
297
+ return applyRules(filePath, [rule], projectRoot);
298
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateWorkloadReport = calculateWorkloadReport;
4
+ const workload_formatter_1 = require("../../utils/core/workload-formatter");
5
+ const rule_helpers_1 = require("../../utils/core/rule-helpers");
6
+ const metadata_1 = require("./metadata");
7
+ const grouping_1 = require("./grouping");
8
+ const hierarchy_calculator_1 = require("./hierarchy-calculator");
9
+ const rules_loader_1 = require("../rules-loader");
10
+ const project_strategy_1 = require("../project-strategy");
11
+ /**
12
+ * Calcule le rapport complet de charge de travail hiérarchique
13
+ */
14
+ function calculateWorkloadReport(projectInfo, matches, rules) {
15
+ // Créer maps pour accès rapide O(1)
16
+ const ruleMap = (0, rule_helpers_1.createRuleMap)(rules);
17
+ const migrationRules = (0, rules_loader_1.loadMigrationRules)();
18
+ const { ruleMigrationMap, rulePriorityMap } = (0, metadata_1.createRuleMetadataMaps)(migrationRules);
19
+ // Grouper matches par target (app/lib)
20
+ const matchesByTarget = (0, grouping_1.groupMatchesByTarget)(matches, projectInfo);
21
+ // Créer la stratégie appropriée
22
+ const strategy = (0, project_strategy_1.createProjectStrategy)(projectInfo);
23
+ // Calculer charge pour chaque target + stats globales en 1 seul parcours
24
+ const targets = [];
25
+ let globalTotalMinutes = 0;
26
+ // Stats globales (calculées en même temps)
27
+ let totalOccurrences = 0;
28
+ let totalSpecialWorkloadFiles = 0;
29
+ let totalRoutineRules = 0;
30
+ const uniqueRulesSet = new Set();
31
+ // Calculer charge pour chaque target (app/lib/monorepo)
32
+ for (const [targetName, targetMatches] of matchesByTarget) {
33
+ // Déterminer le type de target via la stratégie
34
+ const targetType = strategy.getTargetType(projectInfo, targetName);
35
+ // Filtrer les routines selon la stratégie
36
+ const shouldFilterRoutines = strategy.shouldFilterRoutinesFromTarget(targetName, projectInfo);
37
+ const filteredMatches = shouldFilterRoutines
38
+ ? targetMatches.filter(match => {
39
+ const rule = ruleMap.get(match.ruleKey);
40
+ return rule && !(0, rule_helpers_1.isRoutineRule)(rule);
41
+ })
42
+ : targetMatches;
43
+ const migrations = (0, hierarchy_calculator_1.calculateMigrationsForTarget)(filteredMatches, ruleMap, ruleMigrationMap, rulePriorityMap, targetName, projectInfo, rules, false // includeRoutines toujours false (routines viennent des matches ou de target MONOREPO)
44
+ );
45
+ const targetTotalMinutes = migrations.reduce((sum, m) => sum + m.totalTime.minutes, 0);
46
+ targets.push({
47
+ targetName,
48
+ targetType,
49
+ totalTime: (0, workload_formatter_1.convertMinutesToWorkloadTime)(targetTotalMinutes),
50
+ migrations
51
+ });
52
+ // Calculer stats en parcourant migrations
53
+ for (const migration of migrations) {
54
+ for (const priority of migration.priorities) {
55
+ for (const rule of priority.rules) {
56
+ uniqueRulesSet.add(rule.ruleKey);
57
+ if (rule.type === 'routine') {
58
+ totalRoutineRules++;
59
+ }
60
+ else {
61
+ totalOccurrences += rule.occurrences;
62
+ }
63
+ if (rule.specialWorkloadFiles) {
64
+ totalSpecialWorkloadFiles += rule.specialWorkloadFiles;
65
+ }
66
+ }
67
+ }
68
+ }
69
+ globalTotalMinutes += targetTotalMinutes;
70
+ }
71
+ return {
72
+ totalTime: (0, workload_formatter_1.convertMinutesToWorkloadTime)(globalTotalMinutes),
73
+ projectInfo,
74
+ targets,
75
+ stats: {
76
+ totalOccurrences,
77
+ totalSpecialWorkloadFiles,
78
+ totalRoutineRules,
79
+ uniqueRules: uniqueRulesSet.size
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRIORITY_ORDER = exports.MIGRATION_ORDER = void 0;
4
+ /**
5
+ * Ordre d'affichage des migrations (to18 → to19 → to20)
6
+ */
7
+ exports.MIGRATION_ORDER = ['to18', 'to19', 'to20'];
8
+ /**
9
+ * Ordre d'affichage des priorités (obligatoire → recommandé → optionnel)
10
+ */
11
+ exports.PRIORITY_ORDER = [
12
+ 'obligatoire',
13
+ 'recommande',
14
+ 'optionnelle'
15
+ ];
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.groupMatchesByTarget = groupMatchesByTarget;
4
+ exports.groupMatchesByRule = groupMatchesByRule;
5
+ const array_helpers_1 = require("../../utils/core/array-helpers");
6
+ const target_resolver_1 = require("./target-resolver");
7
+ /**
8
+ * Groupe les matches par target (app ou lib) via filePath
9
+ */
10
+ function groupMatchesByTarget(matches, projectInfo) {
11
+ return (0, array_helpers_1.groupBy)(matches, match => (0, target_resolver_1.extractTargetFromFilePath)(match.filePath, projectInfo));
12
+ }
13
+ /**
14
+ * Groupe les matches par ruleKey
15
+ */
16
+ function groupMatchesByRule(matches) {
17
+ return (0, array_helpers_1.groupBy)(matches, match => match.ruleKey);
18
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateMigrationsForTarget = calculateMigrationsForTarget;
4
+ const workload_formatter_1 = require("../../utils/core/workload-formatter");
5
+ const logger_1 = require("../../utils/core/logger");
6
+ const rule_helpers_1 = require("../../utils/core/rule-helpers");
7
+ const grouping_1 = require("./grouping");
8
+ const target_resolver_1 = require("./target-resolver");
9
+ const special_workload_1 = require("./special-workload");
10
+ const constants_1 = require("./constants");
11
+ /**
12
+ * Calcule les migrations pour une target donnée
13
+ */
14
+ function calculateMigrationsForTarget(matches, ruleMap, ruleMigrationMap, rulePriorityMap, targetName, projectInfo, allRules, includeRoutines) {
15
+ const matchesByMigration = new Map();
16
+ for (const match of matches) {
17
+ const migration = ruleMigrationMap.get(match.ruleKey) || 'unknown';
18
+ if (!matchesByMigration.has(migration)) {
19
+ matchesByMigration.set(migration, []);
20
+ }
21
+ matchesByMigration.get(migration).push(match);
22
+ }
23
+ const migrations = [];
24
+ for (const migration of constants_1.MIGRATION_ORDER) {
25
+ const migrationMatches = matchesByMigration.get(migration);
26
+ if (!migrationMatches || migrationMatches.length === 0)
27
+ continue;
28
+ const priorities = calculatePrioritiesForMigration(migrationMatches, ruleMap, rulePriorityMap, targetName, projectInfo, allRules, includeRoutines, migration, ruleMigrationMap);
29
+ const totalMinutes = priorities.reduce((sum, p) => sum + p.totalTime.minutes, 0);
30
+ migrations.push({
31
+ migration,
32
+ totalTime: (0, workload_formatter_1.convertMinutesToWorkloadTime)(totalMinutes),
33
+ priorities
34
+ });
35
+ }
36
+ return migrations;
37
+ }
38
+ /**
39
+ * Calcule les priorités pour une migration donnée
40
+ */
41
+ function calculatePrioritiesForMigration(matches, ruleMap, rulePriorityMap, targetName, projectInfo, allRules, includeRoutines, currentMigration, ruleMigrationMap) {
42
+ const matchesByPriority = new Map();
43
+ for (const match of matches) {
44
+ const priority = rulePriorityMap.get(match.ruleKey) || 'optionnelle';
45
+ if (!matchesByPriority.has(priority)) {
46
+ matchesByPriority.set(priority, []);
47
+ }
48
+ matchesByPriority.get(priority).push(match);
49
+ }
50
+ const priorities = [];
51
+ for (const priority of constants_1.PRIORITY_ORDER) {
52
+ const priorityMatches = matchesByPriority.get(priority);
53
+ if (!priorityMatches || priorityMatches.length === 0)
54
+ continue;
55
+ const rules = calculateRulesForPriority(priorityMatches, ruleMap, targetName, projectInfo, allRules, includeRoutines, currentMigration, priority, ruleMigrationMap, rulePriorityMap);
56
+ const totalMinutes = rules.reduce((sum, r) => sum + r.totalTime.minutes, 0);
57
+ priorities.push({
58
+ priority,
59
+ totalTime: (0, workload_formatter_1.convertMinutesToWorkloadTime)(totalMinutes),
60
+ rules
61
+ });
62
+ }
63
+ return priorities;
64
+ }
65
+ /**
66
+ * Calcule les règles pour une priorité donnée (standard + special + routine)
67
+ */
68
+ function calculateRulesForPriority(matches, ruleMap, targetName, projectInfo, allRules, includeRoutines, currentMigration, currentPriority, ruleMigrationMap, rulePriorityMap) {
69
+ const matchesByRule = (0, grouping_1.groupMatchesByRule)(matches);
70
+ const rules = [];
71
+ // Traiter toutes les règles avec matches (standard + special + routine)
72
+ // Les routines sont maintenant détectées automatiquement via leur match dans le scanner
73
+ for (const [ruleKey, ruleMatches] of matchesByRule) {
74
+ const rule = ruleMap.get(ruleKey);
75
+ if (!rule)
76
+ continue;
77
+ const ruleWorkload = calculateRuleWorkload(rule, ruleMatches, targetName, projectInfo);
78
+ rules.push(ruleWorkload);
79
+ }
80
+ return rules.sort((a, b) => b.totalTime.minutes - a.totalTime.minutes);
81
+ }
82
+ /**
83
+ * Calcule la charge pour une règle unique (avec support special_workload et routine)
84
+ */
85
+ function calculateRuleWorkload(rule, matches, targetName, projectInfo) {
86
+ const occurrences = matches.length;
87
+ let totalMinutes = occurrences * rule.estimated_time_per_occurrence;
88
+ let type = 'standard';
89
+ let specialWorkloadFiles;
90
+ let baseOccurrences;
91
+ // Détecter si c'est une routine (règle avec onFile)
92
+ if ((0, rule_helpers_1.isRoutineRule)(rule)) {
93
+ type = 'routine';
94
+ // Pour les routines, occurrences = 1 (tâche unique par projet)
95
+ totalMinutes = rule.estimated_time_per_occurrence;
96
+ logger_1.logger.debug(` 🔧 Routine [${rule.key}]: 1 × ${rule.estimated_time_per_occurrence}min (tâche unique)`);
97
+ }
98
+ // Support special_workload
99
+ else if (rule.special_workload) {
100
+ const targetPath = (0, target_resolver_1.getTargetPath)(projectInfo, targetName);
101
+ const fileCount = (0, special_workload_1.countSpecialWorkloadFiles)(targetPath, rule, projectInfo.path);
102
+ // Marquer comme special/combined même si fileCount === 0 (règle a special_workload défini)
103
+ specialWorkloadFiles = fileCount;
104
+ baseOccurrences = occurrences;
105
+ type = occurrences > 0 ? 'combined' : 'special';
106
+ if (fileCount > 0) {
107
+ const specialMinutes = fileCount * rule.special_workload.bb_time_by_occurrence;
108
+ logger_1.logger.debug(` 📦 Special workload [${rule.key}]: ${fileCount} fichiers × ${rule.special_workload.bb_time_by_occurrence}min = ${specialMinutes}min`);
109
+ totalMinutes += specialMinutes;
110
+ rule.special_workload.occurrences_found = fileCount;
111
+ }
112
+ }
113
+ const ruleWorkload = {
114
+ ruleKey: rule.key,
115
+ ruleSummary: rule.summary,
116
+ occurrences: type === 'routine' ? 1 : occurrences, // Routine = toujours 1
117
+ minutesPerOccurrence: rule.estimated_time_per_occurrence,
118
+ totalTime: (0, workload_formatter_1.convertMinutesToWorkloadTime)(totalMinutes),
119
+ type
120
+ };
121
+ if (specialWorkloadFiles !== undefined) {
122
+ ruleWorkload.specialWorkloadFiles = specialWorkloadFiles;
123
+ ruleWorkload.baseOccurrences = baseOccurrences;
124
+ ruleWorkload.specialTimePerFile = rule.special_workload?.bb_time_by_occurrence;
125
+ }
126
+ return ruleWorkload;
127
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * Module workload - Calcul hiérarchique de charge de travail
4
+ *
5
+ * API publique :
6
+ * - calculateWorkloadReport() : Fonction principale
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.calculateWorkloadReport = void 0;
10
+ var calculator_1 = require("./calculator");
11
+ Object.defineProperty(exports, "calculateWorkloadReport", { enumerable: true, get: function () { return calculator_1.calculateWorkloadReport; } });
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRuleMetadataMaps = createRuleMetadataMaps;
4
+ /**
5
+ * Crée les maps ruleKey → migration et ruleKey → priority
6
+ * @param migrationRules Structure hiérarchique des règles par migration et priorité
7
+ */
8
+ function createRuleMetadataMaps(migrationRules) {
9
+ const ruleMigrationMap = new Map();
10
+ const rulePriorityMap = new Map();
11
+ for (const [migration, priorities] of Object.entries(migrationRules)) {
12
+ for (const [priority, rulesList] of Object.entries(priorities)) {
13
+ for (const rule of rulesList) {
14
+ ruleMigrationMap.set(rule.key, migration);
15
+ rulePriorityMap.set(rule.key, priority);
16
+ }
17
+ }
18
+ }
19
+ return { ruleMigrationMap, rulePriorityMap };
20
+ }
@@ -0,0 +1,101 @@
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.countSpecialWorkloadFiles = countSpecialWorkloadFiles;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const file_helpers_1 = require("../../utils/core/file-helpers");
40
+ const logger_1 = require("../../utils/core/logger");
41
+ /**
42
+ * Cache pour éviter de recompter les mêmes fichiers
43
+ * Clé: `${targetPath}::${projectRoot}::${pattern1,pattern2,...}`
44
+ */
45
+ const specialWorkloadCache = new Map();
46
+ /**
47
+ * Détermine si un pattern correspond à un fichier racine
48
+ * Fichiers racine = noms exacts sans wildcard (ex: package.json, tsconfig.json)
49
+ * Patterns de scan = patterns avec wildcard (ex: *.module.ts, tsconfig*.json)
50
+ */
51
+ function isRootFilePattern(pattern) {
52
+ // Fichier racine si pas de wildcard (*) et pas de séparateur de chemin (/)
53
+ return !pattern.includes('*') && !pattern.includes('/');
54
+ }
55
+ /**
56
+ * Compte les fichiers correspondant aux patterns special_workload
57
+ * Utilise un cache pour éviter de recompter les mêmes patterns
58
+ *
59
+ * PRIORITÉ : Cherche d'abord les fichiers racine (package.json, tsconfig.json, etc.) dans projectRoot
60
+ * FALLBACK : Scanne récursivement les patterns avec wildcard dans targetPath
61
+ */
62
+ function countSpecialWorkloadFiles(targetPath, rule, projectRoot) {
63
+ if (!rule.special_workload || !rule.special_workload.bb_fileTypes) {
64
+ return 0;
65
+ }
66
+ const patterns = rule.special_workload.bb_fileTypes;
67
+ const cacheKey = `${targetPath}::${projectRoot}::${patterns.join(',')}`;
68
+ // Vérifier le cache
69
+ if (specialWorkloadCache.has(cacheKey)) {
70
+ const cached = specialWorkloadCache.get(cacheKey);
71
+ logger_1.logger.debug(` 📦 Cache hit pour ${rule.key}: ${cached} fichiers`);
72
+ return cached;
73
+ }
74
+ // Compter les fichiers pour chaque pattern
75
+ logger_1.logger.debug(` 🔍 Comptage fichiers special_workload pour ${rule.key}...`);
76
+ let totalCount = 0;
77
+ for (const pattern of patterns) {
78
+ // Détecter si c'est un fichier racine (nom exact sans wildcard)
79
+ if (isRootFilePattern(pattern)) {
80
+ // Vérifier à la racine du projet (ex: package.json, tsconfig.json)
81
+ const rootFilePath = path.join(projectRoot, pattern);
82
+ if (fs.existsSync(rootFilePath)) {
83
+ totalCount += 1;
84
+ logger_1.logger.debug(` • ${pattern}: 1 fichier (racine)`);
85
+ }
86
+ }
87
+ else {
88
+ // Scanner récursivement dans targetPath (ex: *.module.ts, tsconfig*.json)
89
+ const regex = (0, file_helpers_1.buildBlockToRegex)(pattern);
90
+ const count = (0, file_helpers_1.countFiles)(targetPath, regex);
91
+ if (count > 0) {
92
+ logger_1.logger.debug(` • ${pattern}: ${count} fichier(s)`);
93
+ totalCount += count;
94
+ }
95
+ }
96
+ }
97
+ logger_1.logger.debug(` ✓ Total: ${totalCount} fichier(s) trouvé(s)`);
98
+ // Stocker dans le cache
99
+ specialWorkloadCache.set(cacheKey, totalCount);
100
+ return totalCount;
101
+ }