@enokdev/springdocs-mcp 1.1.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.
@@ -0,0 +1,615 @@
1
+ import fetch from 'node-fetch';
2
+ import * as cheerio from 'cheerio';
3
+ import TurndownService from 'turndown';
4
+
5
+ /**
6
+ * Service pour accéder et traiter la documentation Spring Boot
7
+ */
8
+ export class SpringBootDocsService {
9
+ private readonly baseUrl = 'https://docs.spring.io';
10
+ private readonly springProjectsUrl = 'https://spring.io/projects';
11
+ private readonly springGuideUrl = 'https://spring.io/guides';
12
+ private readonly springBootVersion = '3.2.0'; // Version par défaut
13
+ private turndownService: TurndownService;
14
+
15
+ constructor() {
16
+ this.turndownService = new TurndownService({
17
+ headingStyle: 'atx',
18
+ codeBlockStyle: 'fenced',
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Recherche dans tous les projets Spring
24
+ */
25
+ async searchSpringProjects(query: string, limit: number = 10) {
26
+ try {
27
+ const response = await fetch(this.springProjectsUrl);
28
+
29
+ if (!response.ok) {
30
+ throw new Error('Impossible d\'accéder à la page des projets Spring');
31
+ }
32
+
33
+ const html = await response.text();
34
+ const $ = cheerio.load(html);
35
+
36
+ const projects: any[] = [];
37
+
38
+ // Extraire les projets depuis la page
39
+ $('.project-list .project, .project-item, .card').each((index: number, element: any) => {
40
+ const $project = $(element);
41
+ const title = $project.find('h2, h3, .title, .project-title').first().text().trim();
42
+ const description = $project.find('p, .description, .summary').first().text().trim();
43
+ const link = $project.find('a').first().attr('href');
44
+
45
+ if (title && (
46
+ title.toLowerCase().includes(query.toLowerCase()) ||
47
+ description.toLowerCase().includes(query.toLowerCase())
48
+ )) {
49
+ projects.push({
50
+ type: 'spring-project',
51
+ title: title,
52
+ description: description,
53
+ url: link?.startsWith('http') ? link : `https://spring.io${link}`,
54
+ });
55
+ }
56
+ });
57
+
58
+ // Si peu de résultats, ajouter des projets populaires
59
+ if (projects.length < 3) {
60
+ const popularProjects = this.getPopularSpringProjects()
61
+ .filter(project =>
62
+ project.title.toLowerCase().includes(query.toLowerCase()) ||
63
+ project.description.toLowerCase().includes(query.toLowerCase()) ||
64
+ project.keywords.some((k: string) => k.toLowerCase().includes(query.toLowerCase()))
65
+ );
66
+
67
+ projects.push(...popularProjects);
68
+ }
69
+
70
+ return projects.slice(0, limit);
71
+ } catch (error) {
72
+ console.error('Erreur lors de la recherche des projets Spring:', error);
73
+ // Fallback sur les projets populaires
74
+ return this.getPopularSpringProjects()
75
+ .filter(project =>
76
+ project.title.toLowerCase().includes(query.toLowerCase()) ||
77
+ project.description.toLowerCase().includes(query.toLowerCase())
78
+ )
79
+ .slice(0, limit);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Récupère les détails d'un projet Spring spécifique
85
+ */
86
+ async getSpringProject(projectName: string): Promise<string> {
87
+ try {
88
+ const url = `${this.springProjectsUrl}/${projectName}`;
89
+ const response = await fetch(url);
90
+
91
+ if (!response.ok) {
92
+ throw new Error(`Projet non trouvé: ${projectName}`);
93
+ }
94
+
95
+ const html = await response.text();
96
+ const $ = cheerio.load(html);
97
+
98
+ // Extrait le contenu principal du projet
99
+ const content = $('.project-overview, .content, main, .project-details').first();
100
+
101
+ if (content.length === 0) {
102
+ throw new Error('Contenu du projet non trouvé');
103
+ }
104
+
105
+ // Nettoie et convertit en Markdown
106
+ this.cleanHtml($, content);
107
+ const markdown = this.turndownService.turndown(content.html() || '');
108
+
109
+ // Ajouter des informations supplémentaires
110
+ const version = $('.version, .current-version').first().text().trim();
111
+ const status = $('.status, .project-status').first().text().trim();
112
+
113
+ let result = `# Projet Spring: ${projectName}\n\n`;
114
+ if (version) result += `**Version actuelle:** ${version}\n\n`;
115
+ if (status) result += `**Statut:** ${status}\n\n`;
116
+ result += markdown;
117
+
118
+ return result;
119
+ } catch (error) {
120
+ console.error(`Erreur lors de la récupération du projet ${projectName}:`, error);
121
+ throw new Error(`Impossible de récupérer le projet: ${projectName}`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Récupère tous les guides Spring disponibles
127
+ */
128
+ async getAllSpringGuides(category?: string, limit: number = 20) {
129
+ try {
130
+ const response = await fetch(this.springGuideUrl);
131
+
132
+ if (!response.ok) {
133
+ throw new Error('Impossible d\'accéder à la page des guides Spring');
134
+ }
135
+
136
+ const html = await response.text();
137
+ const $ = cheerio.load(html);
138
+
139
+ const guides: any[] = [];
140
+
141
+ // Extraire les guides depuis la page
142
+ $('.guide-list .guide, .guide-item, .card').each((index: number, element: any) => {
143
+ const $guide = $(element);
144
+ const title = $guide.find('h2, h3, .title, .guide-title').first().text().trim();
145
+ const description = $guide.find('p, .description, .summary').first().text().trim();
146
+ const link = $guide.find('a').first().attr('href');
147
+ const guideCategory = $guide.find('.category, .tag').first().text().trim();
148
+
149
+ if (title && (!category || guideCategory.toLowerCase().includes(category.toLowerCase()))) {
150
+ guides.push({
151
+ type: 'spring-guide',
152
+ title: title,
153
+ description: description,
154
+ category: guideCategory,
155
+ url: link?.startsWith('http') ? link : `https://spring.io${link}`,
156
+ id: link?.split('/').pop()?.replace('.html', '') || title.toLowerCase().replace(/\s+/g, '-'),
157
+ });
158
+ }
159
+ });
160
+
161
+ // Si peu de résultats, ajouter des guides populaires
162
+ if (guides.length < 5) {
163
+ const popularGuides = this.getPopularSpringGuides();
164
+ guides.push(...popularGuides);
165
+ }
166
+
167
+ return guides.slice(0, limit);
168
+ } catch (error) {
169
+ console.error('Erreur lors de la récupération des guides Spring:', error);
170
+ // Fallback sur les guides populaires
171
+ return this.getPopularSpringGuides().slice(0, limit);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Liste des projets Spring populaires
177
+ */
178
+ private getPopularSpringProjects() {
179
+ return [
180
+ {
181
+ type: 'spring-project',
182
+ title: 'Spring Boot',
183
+ description: 'Créez des applications Spring de qualité production facilement',
184
+ url: 'https://spring.io/projects/spring-boot',
185
+ keywords: ['boot', 'microservices', 'web', 'standalone'],
186
+ },
187
+ {
188
+ type: 'spring-project',
189
+ title: 'Spring Framework',
190
+ description: 'Le framework Java le plus populaire pour créer des applications d\'entreprise',
191
+ url: 'https://spring.io/projects/spring-framework',
192
+ keywords: ['framework', 'core', 'mvc', 'ioc', 'dependency injection'],
193
+ },
194
+ {
195
+ type: 'spring-project',
196
+ title: 'Spring Data',
197
+ description: 'Fournit un modèle de programmation familier pour l\'accès aux données',
198
+ url: 'https://spring.io/projects/spring-data',
199
+ keywords: ['data', 'jpa', 'mongodb', 'redis', 'repository'],
200
+ },
201
+ {
202
+ type: 'spring-project',
203
+ title: 'Spring Security',
204
+ description: 'Framework de sécurité puissant et hautement personnalisable',
205
+ url: 'https://spring.io/projects/spring-security',
206
+ keywords: ['security', 'authentication', 'authorization', 'oauth'],
207
+ },
208
+ {
209
+ type: 'spring-project',
210
+ title: 'Spring Cloud',
211
+ description: 'Outils pour développer rapidement des patterns de systèmes distribués',
212
+ url: 'https://spring.io/projects/spring-cloud',
213
+ keywords: ['cloud', 'microservices', 'distributed', 'netflix'],
214
+ },
215
+ {
216
+ type: 'spring-project',
217
+ title: 'Spring Integration',
218
+ description: 'Implémentation des Enterprise Integration Patterns',
219
+ url: 'https://spring.io/projects/spring-integration',
220
+ keywords: ['integration', 'messaging', 'endpoints', 'channels'],
221
+ },
222
+ {
223
+ type: 'spring-project',
224
+ title: 'Spring Batch',
225
+ description: 'Framework pour le traitement batch robuste',
226
+ url: 'https://spring.io/projects/spring-batch',
227
+ keywords: ['batch', 'processing', 'jobs', 'steps'],
228
+ },
229
+ {
230
+ type: 'spring-project',
231
+ title: 'Spring WebFlux',
232
+ description: 'Framework web réactif pour Spring',
233
+ url: 'https://spring.io/projects/spring-framework',
234
+ keywords: ['reactive', 'webflux', 'non-blocking', 'async'],
235
+ },
236
+ ];
237
+ }
238
+
239
+ /**
240
+ * Liste des guides Spring populaires
241
+ */
242
+ private getPopularSpringGuides() {
243
+ return [
244
+ {
245
+ type: 'spring-guide',
246
+ title: 'Building a RESTful Web Service',
247
+ description: 'Apprendre à créer un service web RESTful avec Spring Boot',
248
+ category: 'Web',
249
+ url: 'https://spring.io/guides/gs/rest-service/',
250
+ id: 'gs-rest-service',
251
+ },
252
+ {
253
+ type: 'spring-guide',
254
+ title: 'Building an Application with Spring Boot',
255
+ description: 'Apprendre à construire une application avec une configuration minimale',
256
+ category: 'Getting Started',
257
+ url: 'https://spring.io/guides/gs/spring-boot/',
258
+ id: 'gs-spring-boot',
259
+ },
260
+ {
261
+ type: 'spring-guide',
262
+ title: 'Accessing Data with JPA',
263
+ description: 'Apprendre à travailler avec la persistance de données JPA',
264
+ category: 'Data',
265
+ url: 'https://spring.io/guides/gs/accessing-data-jpa/',
266
+ id: 'gs-accessing-data-jpa',
267
+ },
268
+ {
269
+ type: 'spring-guide',
270
+ title: 'Securing a Web Application',
271
+ description: 'Apprendre à protéger votre application web avec Spring Security',
272
+ category: 'Security',
273
+ url: 'https://spring.io/guides/gs/securing-web/',
274
+ id: 'gs-securing-web',
275
+ },
276
+ {
277
+ type: 'spring-guide',
278
+ title: 'Building a RESTful Web Service with Spring Boot Actuator',
279
+ description: 'Apprendre à créer un service prêt pour la production',
280
+ category: 'Production',
281
+ url: 'https://spring.io/guides/gs/actuator-service/',
282
+ id: 'gs-actuator-service',
283
+ },
284
+ {
285
+ type: 'spring-guide',
286
+ title: 'Testing the Web Layer',
287
+ description: 'Apprendre à tester les applications Spring Boot',
288
+ category: 'Testing',
289
+ url: 'https://spring.io/guides/gs/testing-web/',
290
+ id: 'gs-testing-web',
291
+ },
292
+ {
293
+ type: 'spring-guide',
294
+ title: 'Building a Reactive RESTful Web Service',
295
+ description: 'Créer des services web réactifs avec Spring WebFlux',
296
+ category: 'Reactive',
297
+ url: 'https://spring.io/guides/gs/reactive-rest-service/',
298
+ id: 'gs-reactive-rest-service',
299
+ },
300
+ {
301
+ type: 'spring-guide',
302
+ title: 'Consuming a RESTful Web Service',
303
+ description: 'Apprendre à récupérer des données depuis un endpoint REST',
304
+ category: 'Web',
305
+ url: 'https://spring.io/guides/gs/consuming-rest/',
306
+ id: 'gs-consuming-rest',
307
+ },
308
+ ];
309
+ }
310
+ async searchDocumentation(query: string, docType: string = 'all', limit: number = 10) {
311
+ const results: any[] = [];
312
+
313
+ try {
314
+ // Recherche dans les guides
315
+ if (docType === 'all' || docType === 'guides') {
316
+ const guideResults = await this.searchGuides(query, Math.ceil(limit / 3));
317
+ results.push(...guideResults);
318
+ }
319
+
320
+ // Recherche dans la documentation de référence
321
+ if (docType === 'all' || docType === 'reference') {
322
+ const refResults = await this.searchReference(query, Math.ceil(limit / 3));
323
+ results.push(...refResults);
324
+ }
325
+
326
+ // Recherche dans l'API documentation
327
+ if (docType === 'all' || docType === 'api') {
328
+ const apiResults = await this.searchAPI(query, Math.ceil(limit / 3));
329
+ results.push(...apiResults);
330
+ }
331
+
332
+ return results.slice(0, limit);
333
+ } catch (error) {
334
+ console.error('Erreur lors de la recherche dans la documentation:', error);
335
+ throw new Error('Impossible de rechercher dans la documentation Spring Boot');
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Récupère un guide Spring Boot spécifique
341
+ */
342
+ async getGuide(guideId: string): Promise<string> {
343
+ try {
344
+ const url = `${this.baseUrl}/guides/${guideId}/`;
345
+ const response = await fetch(url);
346
+
347
+ if (!response.ok) {
348
+ throw new Error(`Guide non trouvé: ${guideId}`);
349
+ }
350
+
351
+ const html = await response.text();
352
+ const $ = cheerio.load(html);
353
+
354
+ // Extrait le contenu principal du guide
355
+ const content = $('.content, .guide-content, main').first();
356
+
357
+ if (content.length === 0) {
358
+ throw new Error('Contenu du guide non trouvé');
359
+ }
360
+
361
+ // Nettoie et convertit en Markdown
362
+ this.cleanHtml($, content);
363
+ const markdown = this.turndownService.turndown(content.html() || '');
364
+
365
+ return `# Guide Spring Boot: ${guideId}\n\n${markdown}`;
366
+ } catch (error) {
367
+ console.error(`Erreur lors de la récupération du guide ${guideId}:`, error);
368
+ throw new Error(`Impossible de récupérer le guide: ${guideId}`);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Récupère une section de la documentation de référence
374
+ */
375
+ async getReference(section: string, subsection?: string): Promise<string> {
376
+ try {
377
+ let url = `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/reference/html/`;
378
+
379
+ if (subsection) {
380
+ url += `${section}.html#${subsection}`;
381
+ } else {
382
+ url += `${section}.html`;
383
+ }
384
+
385
+ const response = await fetch(url);
386
+
387
+ if (!response.ok) {
388
+ throw new Error(`Section de référence non trouvée: ${section}`);
389
+ }
390
+
391
+ const html = await response.text();
392
+ const $ = cheerio.load(html);
393
+
394
+ // Extrait le contenu principal
395
+ const content = $('.content, .section, main').first();
396
+
397
+ if (content.length === 0) {
398
+ throw new Error('Contenu de la section non trouvé');
399
+ }
400
+
401
+ this.cleanHtml($, content);
402
+ const markdown = this.turndownService.turndown(content.html() || '');
403
+
404
+ return `# Spring Boot Reference: ${section}\n\n${markdown}`;
405
+ } catch (error) {
406
+ console.error(`Erreur lors de la récupération de la référence ${section}:`, error);
407
+ throw new Error(`Impossible de récupérer la section de référence: ${section}`);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Recherche des concepts Spring Boot
413
+ */
414
+ async searchConcepts(concept: string, category?: string) {
415
+ try {
416
+ const concepts = this.getSpringBootConcepts();
417
+ const results: any = {};
418
+
419
+ for (const [cat, conceptList] of Object.entries(concepts)) {
420
+ if (category && cat !== category) continue;
421
+
422
+ const matches = conceptList.filter((c: any) =>
423
+ c.name.toLowerCase().includes(concept.toLowerCase()) ||
424
+ c.description.toLowerCase().includes(concept.toLowerCase()) ||
425
+ c.keywords.some((k: string) => k.toLowerCase().includes(concept.toLowerCase()))
426
+ );
427
+
428
+ if (matches.length > 0) {
429
+ results[cat] = matches;
430
+ }
431
+ }
432
+
433
+ return results;
434
+ } catch (error) {
435
+ console.error('Erreur lors de la recherche de concepts:', error);
436
+ throw new Error('Impossible de rechercher les concepts Spring Boot');
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Recherche dans les guides Spring Boot
442
+ */
443
+ private async searchGuides(query: string, limit: number) {
444
+ // Liste des guides populaires Spring Boot
445
+ const guides = [
446
+ { id: 'gs-rest-service', title: 'Building a RESTful Web Service', description: 'Learn how to create a RESTful web service with Spring Boot' },
447
+ { id: 'gs-spring-boot', title: 'Building an Application with Spring Boot', description: 'Learn how to build an application with minimal configuration' },
448
+ { id: 'gs-accessing-data-jpa', title: 'Accessing Data with JPA', description: 'Learn how to work with JPA data persistence' },
449
+ { id: 'gs-securing-web', title: 'Securing a Web Application', description: 'Learn how to protect your web application with Spring Security' },
450
+ { id: 'gs-consuming-rest', title: 'Consuming a RESTful Web Service', description: 'Learn how to retrieve data from a REST endpoint' },
451
+ { id: 'gs-testing-web', title: 'Testing the Web Layer', description: 'Learn how to test Spring Boot applications' },
452
+ { id: 'gs-actuator-service', title: 'Building a RESTful Web Service with Spring Boot Actuator', description: 'Learn how to create a production-ready service' },
453
+ ];
454
+
455
+ return guides
456
+ .filter(guide =>
457
+ guide.title.toLowerCase().includes(query.toLowerCase()) ||
458
+ guide.description.toLowerCase().includes(query.toLowerCase())
459
+ )
460
+ .slice(0, limit)
461
+ .map(guide => ({
462
+ type: 'guide',
463
+ title: guide.title,
464
+ url: `${this.baseUrl}/guides/${guide.id}/`,
465
+ description: guide.description,
466
+ id: guide.id,
467
+ }));
468
+ }
469
+
470
+ /**
471
+ * Recherche dans la documentation de référence
472
+ */
473
+ private async searchReference(query: string, limit: number) {
474
+ const sections = [
475
+ { name: 'web', title: 'Web Applications', description: 'Building web applications with Spring Boot' },
476
+ { name: 'data', title: 'Data Access', description: 'Working with databases and data sources' },
477
+ { name: 'security', title: 'Security', description: 'Securing Spring Boot applications' },
478
+ { name: 'actuator', title: 'Production-ready Features', description: 'Monitoring and managing applications' },
479
+ { name: 'testing', title: 'Testing', description: 'Testing Spring Boot applications' },
480
+ { name: 'configuration', title: 'Configuration', description: 'Configuring Spring Boot applications' },
481
+ ];
482
+
483
+ return sections
484
+ .filter(section =>
485
+ section.title.toLowerCase().includes(query.toLowerCase()) ||
486
+ section.description.toLowerCase().includes(query.toLowerCase())
487
+ )
488
+ .slice(0, limit)
489
+ .map(section => ({
490
+ type: 'reference',
491
+ title: section.title,
492
+ url: `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/reference/html/${section.name}.html`,
493
+ description: section.description,
494
+ section: section.name,
495
+ }));
496
+ }
497
+
498
+ /**
499
+ * Recherche dans la documentation de l'API
500
+ */
501
+ private async searchAPI(query: string, limit: number) {
502
+ const apiSections = [
503
+ { name: 'Configuration Properties', description: 'Spring Boot configuration properties reference' },
504
+ { name: 'Auto-configuration Classes', description: 'List of auto-configuration classes' },
505
+ { name: 'Starter Dependencies', description: 'Available Spring Boot starters' },
506
+ { name: 'Test Utilities', description: 'Testing utilities and annotations' },
507
+ ];
508
+
509
+ return apiSections
510
+ .filter(section =>
511
+ section.name.toLowerCase().includes(query.toLowerCase()) ||
512
+ section.description.toLowerCase().includes(query.toLowerCase())
513
+ )
514
+ .slice(0, limit)
515
+ .map(section => ({
516
+ type: 'api',
517
+ title: section.name,
518
+ url: `${this.baseUrl}/spring-boot/docs/${this.springBootVersion}/api/`,
519
+ description: section.description,
520
+ }));
521
+ }
522
+
523
+ /**
524
+ * Nettoie le HTML pour une meilleure conversion en Markdown
525
+ */
526
+ private cleanHtml($: cheerio.CheerioAPI, content: any) {
527
+ // Supprime les éléments indésirables
528
+ content.find('script, style, nav, .sidebar, .toc, .feedback').remove();
529
+
530
+ // Nettoie les classes CSS
531
+ content.find('*').each((index: number, element: any) => {
532
+ $(element).removeAttr('class').removeAttr('style').removeAttr('id');
533
+ });
534
+ }
535
+
536
+ /**
537
+ * Base de connaissances des concepts Spring Boot
538
+ */
539
+ private getSpringBootConcepts() {
540
+ return {
541
+ core: [
542
+ {
543
+ name: 'Auto-configuration',
544
+ description: 'Mécanisme qui configure automatiquement Spring Boot basé sur les dépendances présentes',
545
+ keywords: ['auto-configuration', '@EnableAutoConfiguration', 'conditional'],
546
+ examples: ['@SpringBootApplication', '@Conditional', 'spring.factories'],
547
+ },
548
+ {
549
+ name: 'Profiles',
550
+ description: 'Permet de séparer les parties de la configuration d\'application pour différents environnements',
551
+ keywords: ['profiles', '@Profile', 'application.properties'],
552
+ examples: ['application-dev.properties', '@Profile("prod")', 'spring.profiles.active'],
553
+ },
554
+ {
555
+ name: 'Properties',
556
+ description: 'Système de configuration externalisée de Spring Boot',
557
+ keywords: ['properties', '@ConfigurationProperties', '@Value'],
558
+ examples: ['application.yml', '@ConfigurationProperties', 'Environment'],
559
+ },
560
+ ],
561
+ web: [
562
+ {
563
+ name: 'Spring MVC',
564
+ description: 'Framework web intégré pour construire des applications web et des API REST',
565
+ keywords: ['mvc', '@Controller', '@RestController', 'web'],
566
+ examples: ['@GetMapping', '@PostMapping', '@RequestMapping'],
567
+ },
568
+ {
569
+ name: 'Embedded Server',
570
+ description: 'Serveur web intégré (Tomcat, Jetty, Undertow) pour déployer des applications standalone',
571
+ keywords: ['embedded', 'tomcat', 'jetty', 'server'],
572
+ examples: ['server.port', 'TomcatServletWebServerFactory'],
573
+ },
574
+ ],
575
+ data: [
576
+ {
577
+ name: 'Spring Data JPA',
578
+ description: 'Simplifie l\'accès aux données avec JPA/Hibernate',
579
+ keywords: ['jpa', 'hibernate', '@Entity', '@Repository'],
580
+ examples: ['JpaRepository', '@Query', '@Transactional'],
581
+ },
582
+ {
583
+ name: 'Database Migration',
584
+ description: 'Gestion des évolutions de schéma de base de données',
585
+ keywords: ['flyway', 'liquibase', 'migration', 'schema'],
586
+ examples: ['db/migration', 'V1__Initial_schema.sql'],
587
+ },
588
+ ],
589
+ security: [
590
+ {
591
+ name: 'Spring Security',
592
+ description: 'Framework de sécurité pour l\'authentification et l\'autorisation',
593
+ keywords: ['security', 'authentication', 'authorization', '@Secured'],
594
+ examples: ['SecurityConfig', '@PreAuthorize', 'HttpSecurity'],
595
+ },
596
+ ],
597
+ testing: [
598
+ {
599
+ name: 'Test Slices',
600
+ description: 'Tests d\'intégration focalisés sur des couches spécifiques',
601
+ keywords: ['test', '@WebMvcTest', '@DataJpaTest', 'slice'],
602
+ examples: ['@SpringBootTest', '@TestConfiguration', 'TestRestTemplate'],
603
+ },
604
+ ],
605
+ production: [
606
+ {
607
+ name: 'Actuator',
608
+ description: 'Fonctionnalités de monitoring et gestion pour les applications en production',
609
+ keywords: ['actuator', 'monitoring', 'health', 'metrics'],
610
+ examples: ['/actuator/health', '/actuator/metrics', 'HealthIndicator'],
611
+ },
612
+ ],
613
+ };
614
+ }
615
+ }