@silvestv/migration-planificator 5.0.2 → 6.0.1

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 (32) hide show
  1. package/README.fr.md +10 -11
  2. package/README.md +10 -11
  3. package/dist/client.bundle.js +98 -98
  4. package/dist/src/config/migration.config.js +69 -0
  5. package/dist/src/core/app-analyzer.js +69 -79
  6. package/dist/src/core/ast/matchers/html/html-element-matcher.js +32 -7
  7. package/dist/src/core/ast/matchers/html/index.js +36 -4
  8. package/dist/src/core/ast/matchers/ts/collection-matcher.js +18 -0
  9. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +33 -4
  10. package/dist/src/core/ast/matchers/ts/expression-matcher.js +1 -1
  11. package/dist/src/core/ast/matchers/ts/node-matcher.js +4 -0
  12. package/dist/src/core/project-detector.js +94 -106
  13. package/dist/src/core/project-strategy/nx-strategy.js +25 -22
  14. package/dist/src/core/project-strategy/standalone-strategy.js +43 -14
  15. package/dist/src/core/rules-loader.js +21 -13
  16. package/dist/src/core/scan-reporter.js +3 -1
  17. package/dist/src/core/scanner-orchestrator.js +1 -1
  18. package/dist/src/core/workload/constants.js +7 -4
  19. package/dist/src/data/markdown/angular-migration-20-21.md +1010 -0
  20. package/dist/src/data/rules/to19/rules-19-optionnelle.json +3 -0
  21. package/dist/src/data/rules/to21/rules-21-obligatoire.json +423 -0
  22. package/dist/src/data/rules/to21/rules-21-optionnelle.json +253 -0
  23. package/dist/src/data/rules/to21/rules-21-recommande.json +139 -0
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/templates/landing/project-overview.template.js +2 -1
  26. package/dist/src/templates/page/migration-guide.template.js +1 -0
  27. package/dist/src/templates/workload/guide-rule-card.template.js +3 -1
  28. package/dist/src/templates/workload/hierarchy-shared.js +6 -5
  29. package/dist/src/templates/workload/unified-settings-panel.template.js +27 -33
  30. package/dist/src/utils/core/args-parser.js +19 -18
  31. package/dist/styles.css +1 -1
  32. package/package.json +3 -2
@@ -0,0 +1,253 @@
1
+ [
2
+ {
3
+ "key": "signal_forms_experimental",
4
+ "summary": "Signal Forms (API experimentale)",
5
+ "description": "Nouvelle API experimentale de formulaires basee sur les Signals. Utilise form() pour creer un formulaire reactif avec validation declarative via required(), minLength(), maxLength(). Plus besoin de ControlValueAccessor pour les composants custom, la liaison est entierement signal-based.",
6
+ "estimated_time_per_occurrence": 30,
7
+ "onFile": null,
8
+ "fileTypes": ["*.ts"],
9
+ "regex": "import\\s*\\{[^}]*\\bform\\b[^}]*\\}\\s*from\\s*['\"]@angular/forms/signals['\"]",
10
+ "astPattern": {
11
+ "nodeType": "ImportDeclaration",
12
+ "moduleSpecifier": "@angular/forms/signals",
13
+ "namedImports": {
14
+ "containsAny": ["form", "Field", "required", "minLength", "maxLength"]
15
+ },
16
+ "excludeContext": ["StringLiteral", "Comment"]
17
+ },
18
+ "category": "forms",
19
+ "isAutoFixable": false,
20
+ "migration_command": null,
21
+ "risk_level": "medium",
22
+ "code_description": "// Nouvelle API experimentale Signal Forms\nimport { form, Field, required, minLength } from '@angular/forms/signals';\n\n@Component({\n imports: [Field],\n template: `\n <form (ngSubmit)=\"onSubmit()\">\n <input [field]=\"loginForm.email\" type=\"email\" />\n @for (err of loginForm.email().errors(); track $index) {\n @if (err.kind === 'required') {\n <p>Email requis</p>\n }\n }\n <button [disabled]=\"loginForm().invalid()\">Submit</button>\n </form>\n `\n})\nexport class LoginComponent {\n private login = signal({ email: '', password: '' });\n \n loginForm = form(this.login, (path) => {\n required(path.email, { message: 'Email is required' });\n required(path.password);\n minLength(path.password, 6);\n });\n}",
23
+ "doc_url": "https://angular.dev/guide/forms/signal-forms"
24
+ },
25
+ {
26
+ "key": "angular_aria_developer_preview",
27
+ "summary": "Angular Aria (Developer Preview) - composants headless accessibles",
28
+ "description": "Nouvelle bibliotheque @angular/aria en developer preview. Fournit 8 patterns et 13 composants headless accessibles et personnalisables: Accordion, Combobox, Grid, Listbox, Menu, Tabs, Toolbar, Tree. Permet de creer des UIs WCAG-compliant avec un controle total sur le style.",
29
+ "estimated_time_per_occurrence": 20,
30
+ "onFile": null,
31
+ "fileTypes": ["*.ts"],
32
+ "regex": "import\\s*\\{[^}]*\\}\\s*from\\s*['\"]@angular/aria['\"]",
33
+ "astPattern": {
34
+ "nodeType": "ImportDeclaration",
35
+ "moduleSpecifier": "@angular/aria",
36
+ "excludeContext": ["StringLiteral", "Comment"]
37
+ },
38
+ "category": "component",
39
+ "isAutoFixable": false,
40
+ "migration_command": "npm install @angular/aria",
41
+ "risk_level": "low",
42
+ "code_description": "// Installation: npm install @angular/aria\n\nimport { CdkAccordion, CdkAccordionItem } from '@angular/aria';\n\n@Component({\n imports: [CdkAccordion, CdkAccordionItem],\n template: `\n <div cdkAccordion>\n <div cdkAccordionItem #item1=\"cdkAccordionItem\">\n <button (click)=\"item1.toggle()\">Section 1</button>\n @if (item1.expanded) {\n <div>Contenu section 1</div>\n }\n </div>\n </div>\n `\n})\nexport class AccessibleAccordionComponent {}",
43
+ "doc_url": "https://angular.dev/guide/aria"
44
+ },
45
+ {
46
+ "key": "regex_in_templates",
47
+ "summary": "Expressions regulieres supportees dans les templates",
48
+ "description": "Les expressions regulieres sont maintenant supportees directement dans les templates Angular. Permet d'utiliser /pattern/.test() dans @let ou les conditions @if sans passer par le composant. Utile pour les validations simples inline.",
49
+ "estimated_time_per_occurrence": 5,
50
+ "onFile": null,
51
+ "fileTypes": ["*.html", "*.ts"],
52
+ "regex": "@let\\s+\\w+\\s*=\\s*/[^/]+/\\.test\\(",
53
+ "astPattern": {
54
+ "nodeType": "BoundText",
55
+ "textMatches": "/[^/]+/\\.test\\(",
56
+ "excludeContext": ["StringLiteral", "Comment"]
57
+ },
58
+ "category": "template",
59
+ "isAutoFixable": false,
60
+ "migration_command": null,
61
+ "risk_level": "low",
62
+ "code_description": "// Les regex sont maintenant supportees dans les templates\n\n@Component({\n template: `\n @let isValidNumber = /\\\\d+/.test(inputValue());\n \n @if (!isValidNumber) {\n <p>{{ inputValue() }} n'est pas un nombre valide!</p>\n }\n \n @let isEmail = /^[^@]+@[^@]+\\\\.[^@]+$/.test(email());\n @if (!isEmail) {\n <p>Email invalide</p>\n }\n `\n})\nexport class ValidationComponent {\n inputValue = signal('abc');\n email = signal('test@');\n}",
63
+ "doc_url": "https://angular.dev/reference/migrations"
64
+ },
65
+ {
66
+ "key": "defer_viewport_intersection_observer_options",
67
+ "summary": "@defer viewport avec options IntersectionObserver",
68
+ "description": "Le trigger viewport de @defer supporte maintenant les options IntersectionObserver: rootMargin pour declencher avant l'entree dans le viewport, et threshold pour controler le pourcentage de visibilite requis. Ameliore le controle du lazy loading.",
69
+ "estimated_time_per_occurrence": 5,
70
+ "onFile": null,
71
+ "fileTypes": ["*.html", "*.ts"],
72
+ "regex": "@defer\\s*\\(\\s*on\\s+viewport\\s*\\(\\s*\\{[^}]*rootMargin|threshold",
73
+ "astPattern": {
74
+ "nodeType": "DeferredBlock",
75
+ "deferTrigger": {
76
+ "type": "viewport",
77
+ "hasOptions": true
78
+ },
79
+ "excludeContext": ["StringLiteral", "Comment"]
80
+ },
81
+ "category": "template",
82
+ "isAutoFixable": false,
83
+ "migration_command": null,
84
+ "risk_level": "low",
85
+ "code_description": "// Nouvelles options IntersectionObserver pour @defer viewport\n\n@Component({\n template: `\n <!-- Declencher 100px avant d'entrer dans le viewport -->\n @defer (on viewport({ trigger: triggerRef, rootMargin: '100px' })) {\n <heavy-component />\n } @placeholder {\n <div #triggerRef>Loading...</div>\n }\n \n <!-- Avec threshold personnalise -->\n @defer (on viewport({ trigger: el, threshold: 0.5 })) {\n <analytics-widget />\n }\n `\n})\nexport class LazyLoadComponent {}",
86
+ "doc_url": "https://angular.dev/reference/migrations"
87
+ },
88
+ {
89
+ "key": "httpResponse_responseType_cors_diagnostic",
90
+ "summary": "HttpResponse.responseType pour diagnostic CORS",
91
+ "description": "Nouvelle propriete responseType sur HttpResponse pour diagnostiquer les problemes CORS. Retourne 'basic', 'cors', 'opaque' ou 'opaqueredirect'. Une reponse 'opaque' indique un probleme CORS car le contenu n'est pas accessible.",
92
+ "estimated_time_per_occurrence": 5,
93
+ "onFile": null,
94
+ "fileTypes": ["*.ts"],
95
+ "regex": "response\\.responseType",
96
+ "astPattern": {
97
+ "nodeType": "PropertyAccessExpression",
98
+ "propertyName": "responseType",
99
+ "expression": {
100
+ "nodeType": "Identifier",
101
+ "name": "response"
102
+ },
103
+ "excludeContext": ["StringLiteral", "Comment"]
104
+ },
105
+ "category": "http",
106
+ "isAutoFixable": false,
107
+ "migration_command": null,
108
+ "risk_level": "low",
109
+ "code_description": "// Nouvelle propriete pour diagnostiquer les problemes CORS\n\n@Injectable({ providedIn: 'root' })\nexport class DataService {\n private http = inject(HttpClient);\n\n getData(): Observable<any> {\n return this.http.get('/api/data', { observe: 'response' }).pipe(\n tap(response => {\n console.log('Response type:', response.responseType);\n // 'basic' | 'cors' | 'opaque' | 'opaqueredirect'\n \n if (response.responseType === 'opaque') {\n console.warn('CORS issue detected - response is opaque');\n }\n }),\n map(response => response.body)\n );\n }\n}",
110
+ "doc_url": "https://angular.dev/reference/migrations"
111
+ },
112
+ {
113
+ "key": "router_scroll_option",
114
+ "summary": "Router: nouvelle option scroll par navigation",
115
+ "description": "Nouvelle option 'scroll' pour router.navigateByUrl() permettant de controler le comportement du scroll par navigation. 'manual' desactive le scroll meme si active globalement, 'after-transition' suit le comportement global.",
116
+ "estimated_time_per_occurrence": 3,
117
+ "onFile": null,
118
+ "fileTypes": ["*.ts"],
119
+ "regex": "navigateByUrl\\s*\\([^)]+,\\s*\\{[^}]*scroll\\s*:",
120
+ "astPattern": {
121
+ "nodeType": "CallExpression",
122
+ "expression": {
123
+ "nodeType": "PropertyAccessExpression",
124
+ "propertyName": "navigateByUrl"
125
+ },
126
+ "arguments": {
127
+ "properties": {
128
+ "scroll": {
129
+ "exists": true
130
+ }
131
+ }
132
+ },
133
+ "excludeContext": ["StringLiteral", "Comment"]
134
+ },
135
+ "category": "routing",
136
+ "isAutoFixable": false,
137
+ "migration_command": null,
138
+ "risk_level": "low",
139
+ "code_description": "// Nouvelle option scroll pour controler le comportement par navigation\n\n@Component({/*...*/})\nexport class NavigationComponent {\n private router = inject(Router);\n \n navigateWithoutScroll() {\n // Desactive le scroll meme si active globalement\n this.router.navigateByUrl('/target', { scroll: 'manual' });\n }\n \n navigateWithScroll() {\n // Suit le comportement global\n this.router.navigateByUrl('/target', { scroll: 'after-transition' });\n }\n}\n\n// Configuration globale du scroll:\nbootstrapApplication(AppComponent, {\n providers: [\n provideRouter(routes,\n withInMemoryScrolling({ scrollPositionRestoration: 'enabled' })\n )\n ]\n});",
140
+ "doc_url": "https://angular.dev/reference/migrations"
141
+ },
142
+ {
143
+ "key": "tailwind_css_schematic",
144
+ "summary": "Tailwind CSS Schematic integre",
145
+ "description": "Nouveau schematic integre pour configurer Tailwind CSS facilement. Disponible via ng new --style tailwind pour les nouveaux projets ou ng add tailwindcss pour les projets existants. Configure automatiquement postcss et le fichier tailwind.config.js.",
146
+ "estimated_time_per_occurrence": 15,
147
+ "onFile": "package.json",
148
+ "fileTypes": ["package.json"],
149
+ "regex": "^(?![\\s\\S]*\"tailwindcss\"[\\s\\S]*:)[\\s\\S]+",
150
+ "category": "style",
151
+ "isAutoFixable": true,
152
+ "migration_command": "ng add tailwindcss",
153
+ "risk_level": "low",
154
+ "code_description": "// Nouveau schematic pour configurer Tailwind facilement\n\n// Nouvelle application avec Tailwind:\n// ng new my-app --style tailwind\n\n// Ajouter a un projet existant:\n// ng add tailwindcss\n\n// Configure automatiquement:\n// - tailwind.config.js\n// - postcss.config.js\n// - styles.css avec directives @tailwind",
155
+ "doc_url": "https://angular.dev/reference/migrations"
156
+ },
157
+ {
158
+ "key": "ng_serve_define_variables",
159
+ "summary": "Variables --define pour ng serve",
160
+ "description": "ng serve supporte maintenant les variables --define comme ng build depuis v17.2. Permet de definir des constantes remplacees au build (VERSION, API_URL, etc.) sans passer par environment.ts. Utile pour le developpement local avec differentes configurations.",
161
+ "estimated_time_per_occurrence": 5,
162
+ "onFile": null,
163
+ "fileTypes": ["*.ts"],
164
+ "regex": "declare\\s+const\\s+(VERSION|API_URL|BUILD_TIME)\\s*:",
165
+ "astPattern": {
166
+ "nodeType": "VariableStatement",
167
+ "modifiers": ["declare"],
168
+ "declarations": {
169
+ "name": ["VERSION", "API_URL", "BUILD_TIME"]
170
+ },
171
+ "excludeContext": ["StringLiteral", "Comment"]
172
+ },
173
+ "category": "config",
174
+ "isAutoFixable": false,
175
+ "migration_command": null,
176
+ "risk_level": "low",
177
+ "code_description": "// Variables --define disponibles pour ng serve\n\n// Commande:\n// ng serve --define VERSION=\"'1.0.0'\" --define API_URL=\"'http://localhost:3000'\"\n\n// Dans le code:\ndeclare const VERSION: string;\ndeclare const API_URL: string;\n\n// @ts-expect-error defined with --define flag\nconsole.log(`Version: ${VERSION}, API: ${API_URL}`);",
178
+ "doc_url": "https://angular.dev/reference/migrations"
179
+ },
180
+ {
181
+ "key": "experimental_isolated_shadow_dom",
182
+ "summary": "ExperimentalIsolatedShadowDom (encapsulation experimentale)",
183
+ "description": "Nouvelle strategie d'encapsulation experimentale ExperimentalIsolatedShadowDom qui isole completement le composant, y compris des styles globaux. Plus strict que ShadowDom standard. Encore experimental, pas recommande pour production.",
184
+ "estimated_time_per_occurrence": 10,
185
+ "onFile": null,
186
+ "fileTypes": ["*.ts"],
187
+ "regex": "ViewEncapsulation\\.ExperimentalIsolatedShadowDom",
188
+ "astPattern": {
189
+ "nodeType": "PropertyAccessExpression",
190
+ "expression": {
191
+ "nodeType": "Identifier",
192
+ "name": "ViewEncapsulation"
193
+ },
194
+ "propertyName": "ExperimentalIsolatedShadowDom",
195
+ "excludeContext": ["StringLiteral", "Comment"]
196
+ },
197
+ "category": "component",
198
+ "isAutoFixable": false,
199
+ "migration_command": null,
200
+ "risk_level": "high",
201
+ "code_description": "// Nouvelle strategie d'encapsulation experimentale\nimport { ViewEncapsulation } from '@angular/core';\n\n@Component({\n selector: 'app-isolated',\n template: `<div class=\"content\">Isolated content</div>`,\n styles: [`\n .content { color: blue; }\n `],\n encapsulation: ViewEncapsulation.ExperimentalIsolatedShadowDom\n // ATTENTION: Encore experimental, pas recommande pour production\n})\nexport class IsolatedComponent {}",
202
+ "doc_url": "https://angular.dev/reference/migrations"
203
+ },
204
+ {
205
+ "key": "keyvalue_pipe_optional_keys",
206
+ "summary": "KeyValuePipe supporte les cles optionnelles",
207
+ "description": "Le pipe keyvalue supporte maintenant les objets avec des proprietes optionnelles sans erreur TypeScript. Auparavant, iterer sur un objet avec des cles optionnelles causait une erreur de type.",
208
+ "estimated_time_per_occurrence": 2,
209
+ "onFile": null,
210
+ "fileTypes": ["*.html", "*.ts"],
211
+ "regex": "\\|\\s*keyvalue",
212
+ "astPattern": {
213
+ "nodeType": "PipeExpression",
214
+ "pipeName": "keyvalue",
215
+ "excludeContext": ["StringLiteral", "Comment"]
216
+ },
217
+ "category": "template",
218
+ "isAutoFixable": false,
219
+ "migration_command": null,
220
+ "risk_level": "low",
221
+ "code_description": "// keyvalue pipe supporte maintenant les objets avec cles optionnelles\n\ninterface User {\n name: string;\n surname?: string; // Optionnel\n age?: number; // Optionnel\n}\n\n@Component({\n imports: [KeyValuePipe],\n template: `\n @for (prop of user | keyvalue; track $index) {\n <p>{{ prop.key }}: {{ prop.value }}</p>\n }\n `\n})\nexport class UserComponent {\n user: User = { name: 'John' }; // Plus d'erreur TypeScript\n}",
222
+ "doc_url": "https://angular.dev/reference/migrations"
223
+ },
224
+ {
225
+ "key": "cldr_updated_v47",
226
+ "summary": "CLDR mis a jour (v41 → v47)",
227
+ "description": "Les donnees de localisation CLDR sont mises a jour de la version 41 vers 47. Peut affecter legerement le formatage des dates, nombres et devises selon les locales. Verifier vos tests de formatage localise apres la migration.",
228
+ "estimated_time_per_occurrence": 10,
229
+ "onFile": null,
230
+ "fileTypes": ["*.spec.ts"],
231
+ "regex": "expect\\([^)]*\\.(toContain|toEqual|toBe)\\s*\\([^)]*\\d{1,2}[/.-]\\d{1,2}[/.-]\\d{2,4}",
232
+ "astPattern": {
233
+ "nodeType": "CallExpression",
234
+ "expression": {
235
+ "nodeType": "PropertyAccessExpression",
236
+ "propertyName": ["toContain", "toBe", "toEqual"]
237
+ },
238
+ "arguments": {
239
+ "contains": {
240
+ "nodeType": "StringLiteral",
241
+ "valueMatches": "\\d{1,2}[/.-]\\d{1,2}[/.-]\\d{2,4}"
242
+ }
243
+ },
244
+ "excludeContext": ["Comment"]
245
+ },
246
+ "category": "test",
247
+ "isAutoFixable": false,
248
+ "migration_command": null,
249
+ "risk_level": "low",
250
+ "code_description": "// Mise a jour automatique des donnees de localisation CLDR v41 -> v47\n// Peut affecter le formatage des dates, nombres, devises\n\n// Verifier vos tests de formatage localise:\nit('should format date', () => {\n // Le format peut avoir legerement change\n expect(pipe.transform(date, 'short')).toContain('/');\n});\n\n// Tester avec differentes locales:\nregisterLocaleData(localeFr);\nregisterLocaleData(localeDe);",
251
+ "doc_url": "https://angular.dev/reference/migrations"
252
+ }
253
+ ]
@@ -0,0 +1,139 @@
1
+ [
2
+ {
3
+ "key": "vitest_migration",
4
+ "summary": "Migrer vers Vitest (test runner par defaut)",
5
+ "description": "Vitest est maintenant le test runner par defaut et stable en Angular 21. Karma/Jasmine restent supportes mais Vitest offre un demarrage instantane et un watch mode performant. La migration est experimentale et necessite des ajustements manuels. Note: fakeAsync doit etre reecrit avec vi.useFakeTimers().",
6
+ "estimated_time_per_occurrence": 10,
7
+ "onFile": "angular.json",
8
+ "onFileNx": "project.json",
9
+ "fileTypes": ["angular.json", "project.json"],
10
+ "regex": "\"builder\"\\s*:\\s*\"@angular-devkit/build-angular:karma\"",
11
+ "category": "test",
12
+ "isAutoFixable": false,
13
+ "migration_command": null,
14
+ "risk_level": "medium",
15
+ "code_description": "// Migration vers Vitest (experimentale)\n\n// 1. Installer Vitest:\nnpm install vitest jsdom --save-dev\n\n// 2. Modifier angular.json:\n\"test\": {\n \"builder\": \"@angular/build:unit-test\",\n \"options\": {\n \"runner\": \"vitest\"\n }\n}\n\n// 3. Adapter les tests fakeAsync:\n// Avant - Jasmine:\nit('test', fakeAsync(() => {\n tick(1000);\n expect(value).toBe(true);\n}));\n\n// Apres - Vitest:\nimport { vi } from 'vitest';\nit('test', () => {\n vi.useFakeTimers();\n vi.advanceTimersByTime(1000);\n expect(value).toBe(true);\n vi.useRealTimers();\n});",
16
+ "doc_url": "https://angular.dev/guide/testing/migrating-to-vitest"
17
+ },
18
+ {
19
+ "key": "httpClient_provided_by_default",
20
+ "summary": "HttpClient fourni par defaut (provideHttpClient() optionnel)",
21
+ "description": "HttpClient est maintenant fourni dans le root injector par defaut en Angular 21. provideHttpClient() n'est plus necessaire sauf pour personnalisation (ajout d'interceptors, withFetch, etc.). Simplifier votre configuration en retirant les appels provideHttpClient() sans options.",
22
+ "estimated_time_per_occurrence": 3,
23
+ "onFile": null,
24
+ "fileTypes": ["*.ts"],
25
+ "regex": "provideHttpClient\\s*\\(\\s*\\)",
26
+ "astPattern": {
27
+ "nodeType": "CallExpression",
28
+ "functionName": "provideHttpClient",
29
+ "arguments": {
30
+ "count": 0
31
+ },
32
+ "excludeContext": ["StringLiteral", "Comment"]
33
+ },
34
+ "category": "http",
35
+ "isAutoFixable": true,
36
+ "migration_command": null,
37
+ "risk_level": "low",
38
+ "code_description": "// HttpClient fourni par defaut en Angular 21\n\n// Avant - provideHttpClient necessaire:\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideHttpClient(), // N'est plus necessaire\n ]\n};\n\n// Apres - Configuration basique:\nexport const appConfig: ApplicationConfig = {\n providers: [\n // HttpClient disponible automatiquement\n ]\n};\n\n// Avec interceptors (provideHttpClient toujours requis):\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideHttpClient(\n withInterceptors([authInterceptor]),\n withFetch() // Optionnel\n )\n ]\n};",
39
+ "doc_url": "https://angular.dev/guide/http"
40
+ },
41
+ {
42
+ "key": "simpleChanges_generic_typesafe",
43
+ "summary": "SimpleChanges generique (Type-Safe)",
44
+ "description": "SimpleChanges supporte maintenant les generiques en Angular 21, permettant un typage fort des proprietes @Input dans ngOnChanges. Utiliser SimpleChanges<ComponentClass> pour obtenir l'autocompletion et la verification de type sur currentValue et previousValue.",
45
+ "estimated_time_per_occurrence": 3,
46
+ "onFile": null,
47
+ "fileTypes": ["*.ts"],
48
+ "regex": "ngOnChanges\\s*\\(\\s*changes\\s*:\\s*SimpleChanges\\s*\\)",
49
+ "astPattern": {
50
+ "nodeType": "MethodDeclaration",
51
+ "name": "ngOnChanges",
52
+ "parameters": {
53
+ "first": {
54
+ "type": "SimpleChanges"
55
+ }
56
+ },
57
+ "inClass": {
58
+ "implements": "OnChanges"
59
+ },
60
+ "excludeContext": ["StringLiteral", "Comment"]
61
+ },
62
+ "category": "component",
63
+ "isAutoFixable": false,
64
+ "migration_command": null,
65
+ "risk_level": "low",
66
+ "code_description": "// SimpleChanges supporte maintenant les generiques\n\n// Avant - any implicite:\nngOnChanges(changes: SimpleChanges): void {\n const nameChange = changes['name']; // SimpleChange avec any\n}\n\n// Apres - Type-safe:\nngOnChanges(changes: SimpleChanges<UserComponent>): void {\n if (changes.age) {\n const newAge: number = changes.age.currentValue; // Type!\n const oldAge: number | undefined = changes.age.previousValue;\n console.log(`Age changed from ${oldAge} to ${newAge}`);\n }\n}",
67
+ "doc_url": "https://angular.dev/api/core/SimpleChanges"
68
+ },
69
+ {
70
+ "key": "ngClass_to_class_binding",
71
+ "summary": "Migrer NgClass vers [class] binding natif",
72
+ "description": "NgClass peut etre remplace par le binding [class] natif qui offre les memes fonctionnalites sans import supplementaire. Le schematic officiel automatise cette migration. Reduit la taille du bundle et simplifie le code.",
73
+ "estimated_time_per_occurrence": 2,
74
+ "onFile": null,
75
+ "fileTypes": ["*.html", "*.ts"],
76
+ "regex": "\\[ngClass\\]",
77
+ "astPattern": {
78
+ "nodeType": "BoundAttribute",
79
+ "name": "ngClass",
80
+ "excludeContext": ["StringLiteral", "Comment"]
81
+ },
82
+ "category": "template",
83
+ "isAutoFixable": true,
84
+ "migration_command": "ng g @angular/core:ngclass-to-class-migration",
85
+ "risk_level": "low",
86
+ "code_description": "// Schematic de migration automatique:\n// ng g @angular/core:ngclass-to-class-migration\n\n// Avant:\n@Component({\n imports: [NgClass],\n template: `\n <button [ngClass]=\"{ 'active': isActive(), 'disabled': isDisabled }\">\n Click\n </button>\n `\n})\n\n// Apres:\n@Component({\n // imports: [NgClass] - Plus necessaire\n template: `\n <button [class]=\"{ 'active': isActive(), 'disabled': isDisabled }\">\n Click\n </button>\n `\n})",
87
+ "doc_url": "https://blog.ninja-squad.com/2025/11/20/what-is-new-angular-21.0"
88
+ },
89
+ {
90
+ "key": "ngStyle_to_style_binding",
91
+ "summary": "Migrer NgStyle vers [style] binding natif",
92
+ "description": "NgStyle peut etre remplace par le binding [style] natif qui offre les memes fonctionnalites sans import supplementaire. Le schematic officiel automatise cette migration. Reduit la taille du bundle et simplifie le code.",
93
+ "estimated_time_per_occurrence": 2,
94
+ "onFile": null,
95
+ "fileTypes": ["*.html", "*.ts"],
96
+ "regex": "\\[ngStyle\\]",
97
+ "astPattern": {
98
+ "nodeType": "BoundAttribute",
99
+ "name": "ngStyle",
100
+ "excludeContext": ["StringLiteral", "Comment"]
101
+ },
102
+ "category": "template",
103
+ "isAutoFixable": true,
104
+ "migration_command": "ng g @angular/core:ngstyle-to-style-migration",
105
+ "risk_level": "low",
106
+ "code_description": "// Schematic de migration automatique:\n// ng g @angular/core:ngstyle-to-style-migration\n\n// Avant:\n@Component({\n imports: [NgStyle],\n template: `\n <div [ngStyle]=\"{ 'background-color': bgColor(), 'font-size': fontSize }\">\n Content\n </div>\n `\n})\n\n// Apres:\n@Component({\n // imports: [NgStyle] - Plus necessaire\n template: `\n <div [style]=\"{ 'background-color': bgColor(), 'font-size': fontSize }\">\n Content\n </div>\n `\n})",
107
+ "doc_url": "https://blog.ninja-squad.com/2025/11/20/what-is-new-angular-21.0"
108
+ },
109
+ {
110
+ "key": "commonModule_to_standalone_imports",
111
+ "summary": "Migrer CommonModule vers imports standalone individuels",
112
+ "description": "CommonModule peut etre remplace par des imports individuels des pipes et directives necessaires (UpperCasePipe, DatePipe, AsyncPipe, etc.). Le schematic officiel analyse automatiquement les templates et ajoute uniquement les imports requis. Ameliore le tree-shaking.",
113
+ "estimated_time_per_occurrence": 5,
114
+ "onFile": null,
115
+ "fileTypes": ["*.ts"],
116
+ "regex": "imports\\s*:\\s*\\[[^\\]]*CommonModule[^\\]]*\\]",
117
+ "astPattern": {
118
+ "nodeType": "Decorator",
119
+ "name": "Component",
120
+ "properties": {
121
+ "imports": {
122
+ "elements": {
123
+ "contains": {
124
+ "nodeType": "Identifier",
125
+ "name": "CommonModule"
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "excludeContext": ["StringLiteral", "Comment"]
131
+ },
132
+ "category": "imports",
133
+ "isAutoFixable": true,
134
+ "migration_command": "ng g @angular/core:common-to-standalone",
135
+ "risk_level": "low",
136
+ "code_description": "// Schematic de migration automatique:\n// ng g @angular/core:common-to-standalone\n\n// Avant:\nimport { CommonModule } from '@angular/common';\n\n@Component({\n imports: [CommonModule], // Import global\n template: `\n @for (item of items; track item.id) {\n <div>{{ item.name | uppercase }}</div>\n }\n <div>{{ data$ | async }}</div>\n `\n})\n\n// Apres:\nimport { UpperCasePipe, AsyncPipe } from '@angular/common';\n\n@Component({\n imports: [UpperCasePipe, AsyncPipe], // Imports individuels\n template: `\n @for (item of items; track item.id) {\n <div>{{ item.name | uppercase }}</div>\n }\n <div>{{ data$ | async }}</div>\n `\n})",
137
+ "doc_url": "https://angular.dev/reference/migrations/standalone"
138
+ }
139
+ ]
package/dist/src/index.js CHANGED
@@ -121,7 +121,7 @@ function generateReport() {
121
121
  const ruleMigrationMap = {};
122
122
  const allRulesForGuide = [];
123
123
  // Parcourir toutes les migrations et priorités pour créer les maps ET la liste complète
124
- for (const migration of ['to18', 'to19', 'to20']) {
124
+ for (const migration of ['to18', 'to19', 'to20', 'to21']) {
125
125
  for (const priority of ['obligatoire', 'recommande', 'optionnelle']) {
126
126
  migrationRulesStructure[migration][priority].forEach(rule => {
127
127
  rulePriorityMap[rule.key] = priority;
@@ -8,7 +8,7 @@ function renderProjectOverview(data) {
8
8
  Rapport de Migration Angular
9
9
  </h1>
10
10
  <p class="text-gray-600 text-lg mb-4">
11
- Analyse et planification de charge pour migrations Angular 17→18, 18→19, 19→20
11
+ Analyse et planification de charge pour migrations Angular 17→18, 18→19, 19→20, 20→21
12
12
  </p>
13
13
  <div class="bg-gray-50 rounded-lg p-4 mb-6">
14
14
  <h3 class="text-lg font-semibold text-gray-700 mb-2">Projet analysé</h3>
@@ -21,6 +21,7 @@ function renderProjectOverview(data) {
21
21
  <span class="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg font-medium">Angular 17→18</span>
22
22
  <span class="px-4 py-2 bg-indigo-100 text-indigo-700 rounded-lg font-medium">Angular 18→19</span>
23
23
  <span class="px-4 py-2 bg-purple-100 text-purple-700 rounded-lg font-medium">Angular 19→20</span>
24
+ <span class="px-4 py-2 bg-pink-100 text-pink-700 rounded-lg font-medium">Angular 20→21</span>
24
25
  </div>
25
26
  </header>
26
27
  `;
@@ -52,6 +52,7 @@ function renderMigrationGuidePage(data, cssContent, jsContent, timestamp, versio
52
52
  <option value="to18">Angular 17 → 18</option>
53
53
  <option value="to19">Angular 18 → 19</option>
54
54
  <option value="to20">Angular 19 → 20</option>
55
+ <option value="to21">Angular 20 → 21</option>
55
56
  </select>
56
57
  </div>
57
58
 
@@ -79,7 +79,7 @@ function renderGuideRuleCard(rule, matches) {
79
79
  `;
80
80
  }
81
81
  /**
82
- * Extrait la migration (to18, to19, to20) depuis la clé de règle
82
+ * Extrait la migration (to18, to19, to20, to21) depuis la clé de règle
83
83
  * Ex: "angular_18_environment" -> "to18"
84
84
  */
85
85
  function getMigrationFromRuleKey(ruleKey) {
@@ -89,6 +89,8 @@ function getMigrationFromRuleKey(ruleKey) {
89
89
  return 'to19';
90
90
  if (ruleKey.includes('_20_'))
91
91
  return 'to20';
92
+ if (ruleKey.includes('_21_'))
93
+ return 'to21';
92
94
  return 'unknown';
93
95
  }
94
96
  /**
@@ -14,8 +14,9 @@ exports.renderFlatPriorityContent = renderFlatPriorityContent;
14
14
  exports.renderFlatMigrationContent = renderFlatMigrationContent;
15
15
  exports.renderFlatRulesContent = renderFlatRulesContent;
16
16
  const core_1 = require("../../utils/core");
17
+ const migration_config_1 = require("../../config/migration.config");
17
18
  /**
18
- * Render Migration (niveau 2 : to18, to19, to20)
19
+ * Render Migration (niveau 2 : to18, to19, to20, to21)
19
20
  * @param idSuffix - Suffixe optionnel pour rendre les IDs uniques (ex: pour vues merged)
20
21
  */
21
22
  function renderMigration(targetName, migration, idSuffix) {
@@ -24,8 +25,8 @@ function renderMigration(targetName, migration, idSuffix) {
24
25
  const sectionId = `section-${targetName}-${migration.migration}${suffix}`;
25
26
  // ID pour les data-* (standard, sans suffixe, pour le système de mise à jour)
26
27
  const migrationId = `${targetName}-${migration.migration}`;
27
- const colorMap = { to18: 'blue', to19: 'indigo', to20: 'purple' };
28
- const color = colorMap[migration.migration] || 'gray';
28
+ // Couleur depuis la config centralisée
29
+ const color = migration_config_1.MIGRATION_COLOR_MAP[migration.migration] || 'gray';
29
30
  return `
30
31
  <div id="${sectionId}" class="ml-8 border-l-4 border-${color}-400 pl-4" data-migration="${migration.migration}" data-layer="migration">
31
32
  <div class="flex items-center justify-between bg-${color}-50 p-3 rounded-lg cursor-pointer" data-collapse-trigger="${sectionId}">
@@ -389,8 +390,8 @@ function renderFlatMigration(targetName, migrationName, totalMinutes, rules, idS
389
390
  const migrationId = `${targetName}-flat-${migrationName}`;
390
391
  const suffix = idSuffix ? `-${idSuffix}` : '';
391
392
  const sectionId = `section-flat-${migrationId}${suffix}`;
392
- const colorMap = { to18: 'blue', to19: 'indigo', to20: 'purple' };
393
- const color = colorMap[migrationName] || 'gray';
393
+ // Couleur depuis la config centralisée
394
+ const color = migration_config_1.MIGRATION_COLOR_MAP[migrationName] || 'gray';
394
395
  const totalTime = {
395
396
  minutes: totalMinutes,
396
397
  hours: Math.round((totalMinutes / 60) * 100) / 100,
@@ -2,13 +2,38 @@
2
2
  /**
3
3
  * Template Unified Settings Panel - Réglages migrations + priorités
4
4
  * Regroupement des filtres de migrations et priorités dans un seul bloc
5
+ * Génération dynamique depuis MIGRATION_CONFIGS (DRY)
5
6
  */
6
7
  Object.defineProperty(exports, "__esModule", { value: true });
7
8
  exports.renderUnifiedSettingsPanel = renderUnifiedSettingsPanel;
9
+ const migration_config_1 = require("../../config/migration.config");
10
+ /**
11
+ * Génère les checkboxes de migration dynamiquement depuis la config
12
+ */
13
+ function renderMigrationCheckboxes() {
14
+ return migration_config_1.MIGRATION_CONFIGS.map(config => `
15
+ <label class="flex items-center gap-3 px-4 py-3 bg-white rounded-lg cursor-pointer hover:bg-blue-50 transition-colors border border-blue-200">
16
+ <input
17
+ type="checkbox"
18
+ data-migration-select="${config.version}"
19
+ class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
20
+ checked
21
+ aria-label="Migration vers Angular ${config.targetVersion}"
22
+ />
23
+ <span class="text-base font-medium text-gray-800">${config.label}</span>
24
+ </label>`).join('\n');
25
+ }
26
+ /**
27
+ * Calcule le nombre total de filtres (migrations + priorités)
28
+ */
29
+ function getTotalFiltersCount() {
30
+ return migration_config_1.MIGRATION_CONFIGS.length + 3; // 3 priorités
31
+ }
8
32
  /**
9
33
  * Génère le panel de réglages unifié
10
34
  */
11
35
  function renderUnifiedSettingsPanel() {
36
+ const totalFilters = getTotalFiltersCount();
12
37
  return `
13
38
  <div class="bg-white rounded-2xl shadow-xl p-6 mb-8">
14
39
  <!-- Header avec titre et badge global -->
@@ -16,7 +41,7 @@ function renderUnifiedSettingsPanel() {
16
41
  <h2 class="text-2xl font-bold text-gray-800 flex items-center">
17
42
  ⚙️ Réglages de Filtrage
18
43
  <span data-total-filters-count class="ml-3 px-3 py-1.5 bg-green-100 text-green-700 text-sm font-semibold rounded-full">
19
- 6/6 actifs
44
+ ${totalFilters}/${totalFilters} actifs
20
45
  </span>
21
46
  </h2>
22
47
  </div>
@@ -40,38 +65,7 @@ function renderUnifiedSettingsPanel() {
40
65
  </div>
41
66
 
42
67
  <div class="space-y-3">
43
- <label class="flex items-center gap-3 px-4 py-3 bg-white rounded-lg cursor-pointer hover:bg-blue-50 transition-colors border border-blue-200">
44
- <input
45
- type="checkbox"
46
- data-migration-select="to18"
47
- class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
48
- checked
49
- aria-label="Migration vers Angular 18"
50
- />
51
- <span class="text-base font-medium text-gray-800">Angular 17 → 18</span>
52
- </label>
53
-
54
- <label class="flex items-center gap-3 px-4 py-3 bg-white rounded-lg cursor-pointer hover:bg-blue-50 transition-colors border border-blue-200">
55
- <input
56
- type="checkbox"
57
- data-migration-select="to19"
58
- class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
59
- checked
60
- aria-label="Migration vers Angular 19"
61
- />
62
- <span class="text-base font-medium text-gray-800">Angular 18 → 19</span>
63
- </label>
64
-
65
- <label class="flex items-center gap-3 px-4 py-3 bg-white rounded-lg cursor-pointer hover:bg-blue-50 transition-colors border border-blue-200">
66
- <input
67
- type="checkbox"
68
- data-migration-select="to20"
69
- class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
70
- checked
71
- aria-label="Migration vers Angular 20"
72
- />
73
- <span class="text-base font-medium text-gray-800">Angular 19 → 20</span>
74
- </label>
68
+ ${renderMigrationCheckboxes()}
75
69
  </div>
76
70
  </div>
77
71