@enokdev/springdocs-mcp 1.2.4 → 1.2.7
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.
- package/build/index.js +5 -5
- package/build/index.js.map +1 -1
- package/build/services/advanced-features-old.d.ts +45 -0
- package/build/services/advanced-features-old.d.ts.map +1 -0
- package/build/services/advanced-features-old.js +792 -0
- package/build/services/advanced-features-old.js.map +1 -0
- package/build/services/advanced-features.d.ts +20 -14
- package/build/services/advanced-features.d.ts.map +1 -1
- package/build/services/advanced-features.js +368 -476
- package/build/services/advanced-features.js.map +1 -1
- package/build/services/springboot-docs-optimized-old.d.ts +55 -0
- package/build/services/springboot-docs-optimized-old.d.ts.map +1 -0
- package/build/services/springboot-docs-optimized-old.js +588 -0
- package/build/services/springboot-docs-optimized-old.js.map +1 -0
- package/build/services/springboot-docs-optimized.d.ts +30 -21
- package/build/services/springboot-docs-optimized.d.ts.map +1 -1
- package/build/services/springboot-docs-optimized.js +246 -371
- package/build/services/springboot-docs-optimized.js.map +1 -1
- package/build/tools/index.d.ts +22 -0
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +13 -1
- package/build/tools/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,28 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import * as cheerio from 'cheerio';
|
|
3
|
+
import TurndownService from 'turndown';
|
|
1
4
|
import { CacheService } from './cache.js';
|
|
2
5
|
/**
|
|
3
|
-
* Advanced features service for Spring documentation
|
|
6
|
+
* Advanced features service for Spring documentation - uses ONLY real Spring documentation APIs
|
|
7
|
+
* No mock data - everything is fetched from actual Spring documentation sources
|
|
4
8
|
*/
|
|
5
9
|
export class AdvancedFeaturesService {
|
|
6
10
|
cache;
|
|
11
|
+
turndownService;
|
|
12
|
+
baseUrl = 'https://docs.spring.io';
|
|
13
|
+
springProjectsUrl = 'https://spring.io/projects';
|
|
14
|
+
springGuideUrl = 'https://spring.io/guides';
|
|
15
|
+
REQUEST_TIMEOUT = 10000;
|
|
16
|
+
MAX_RETRIES = 3;
|
|
7
17
|
constructor() {
|
|
8
18
|
this.cache = new CacheService();
|
|
19
|
+
this.turndownService = new TurndownService({
|
|
20
|
+
headingStyle: 'atx',
|
|
21
|
+
codeBlockStyle: 'fenced',
|
|
22
|
+
});
|
|
9
23
|
}
|
|
10
24
|
/**
|
|
11
|
-
* Search across the entire Spring ecosystem
|
|
25
|
+
* Search across the entire Spring ecosystem using real APIs
|
|
12
26
|
*/
|
|
13
27
|
async searchEcosystem(query, scope = 'all', limit = 5) {
|
|
14
28
|
const cacheKey = `ecosystem:${query}:${scope}:${limit}`;
|
|
@@ -36,539 +50,417 @@ export class AdvancedFeaturesService {
|
|
|
36
50
|
}
|
|
37
51
|
results.totalResults = Object.values(results.categories)
|
|
38
52
|
.reduce((total, category) => total + (category?.length || 0), 0);
|
|
39
|
-
this.
|
|
40
|
-
|
|
53
|
+
const formattedResult = this.formatEcosystemResults(results);
|
|
54
|
+
this.cache.set(cacheKey, formattedResult);
|
|
55
|
+
return formattedResult;
|
|
41
56
|
}
|
|
42
57
|
catch (error) {
|
|
43
|
-
console.error('Error
|
|
44
|
-
|
|
58
|
+
console.error('Error searching ecosystem:', error);
|
|
59
|
+
return `# Spring Ecosystem Search Results\n\nError searching for "${query}": ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extract content intelligently - same as in SpringBootDocsServiceOptimized
|
|
64
|
+
*/
|
|
65
|
+
extractIntelligentContent(markdown, detailLevel = 'medium') {
|
|
66
|
+
const limits = {
|
|
67
|
+
'summary': 1500,
|
|
68
|
+
'medium': 4000,
|
|
69
|
+
'full': 8000
|
|
70
|
+
};
|
|
71
|
+
const maxLength = limits[detailLevel] || limits['medium'];
|
|
72
|
+
if (markdown.length <= maxLength)
|
|
73
|
+
return markdown;
|
|
74
|
+
const lines = markdown.split('\n');
|
|
75
|
+
let result = '';
|
|
76
|
+
let inCodeBlock = false;
|
|
77
|
+
let codeBlockContent = '';
|
|
78
|
+
let currentLength = 0;
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (line.trim().startsWith('```')) {
|
|
81
|
+
inCodeBlock = !inCodeBlock;
|
|
82
|
+
if (!inCodeBlock && codeBlockContent) {
|
|
83
|
+
const blockToAdd = codeBlockContent + line + '\n';
|
|
84
|
+
if (currentLength + blockToAdd.length <= maxLength * 0.8) {
|
|
85
|
+
result += blockToAdd;
|
|
86
|
+
currentLength += blockToAdd.length;
|
|
87
|
+
}
|
|
88
|
+
codeBlockContent = '';
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
codeBlockContent = line + '\n';
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (inCodeBlock) {
|
|
96
|
+
codeBlockContent += line + '\n';
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (line.startsWith('#') || line.startsWith('-') || line.startsWith('*') || line.trim().startsWith('>')) {
|
|
100
|
+
if (currentLength + line.length + 1 <= maxLength) {
|
|
101
|
+
result += line + '\n';
|
|
102
|
+
currentLength += line.length + 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (line.trim() && currentLength + line.length + 1 <= maxLength) {
|
|
106
|
+
result += line + '\n';
|
|
107
|
+
currentLength += line.length + 1;
|
|
108
|
+
}
|
|
109
|
+
if (currentLength >= maxLength)
|
|
110
|
+
break;
|
|
45
111
|
}
|
|
112
|
+
return result.trim();
|
|
46
113
|
}
|
|
47
114
|
/**
|
|
48
|
-
* Get
|
|
115
|
+
* Get tutorials by fetching from actual Spring Boot guides
|
|
49
116
|
*/
|
|
50
|
-
async getTutorial(topic, level = 'beginner') {
|
|
51
|
-
const cacheKey = `tutorial:${topic}:${level}`;
|
|
117
|
+
async getTutorial(topic, level = 'beginner', detailLevel = 'medium') {
|
|
118
|
+
const cacheKey = `tutorial:${topic}:${level}:${detailLevel}`;
|
|
52
119
|
const cached = this.cache.get(cacheKey);
|
|
53
120
|
if (cached)
|
|
54
121
|
return cached;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
122
|
+
try {
|
|
123
|
+
const guides = await this.searchGuides(topic, 3);
|
|
124
|
+
if (guides.length === 0) {
|
|
125
|
+
return `# Tutorial Not Found\n\nNo tutorials found for topic: "${topic}"\n\nTry searching for: rest-api, jpa, security, testing, or web`;
|
|
126
|
+
}
|
|
127
|
+
const guide = guides[0];
|
|
128
|
+
const response = await this.fetchWithRetry(guide.url);
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new Error(`Failed to fetch tutorial: ${response.status}`);
|
|
131
|
+
}
|
|
132
|
+
const html = await response.text();
|
|
133
|
+
const $ = cheerio.load(html);
|
|
134
|
+
const content = $('.content, .guide-content, main, .markdown-body').first();
|
|
135
|
+
if (content.length === 0) {
|
|
136
|
+
throw new Error('No content found in guide');
|
|
137
|
+
}
|
|
138
|
+
const markdown = this.turndownService.turndown(content.html() || '');
|
|
139
|
+
const extractedContent = this.extractIntelligentContent(markdown, detailLevel);
|
|
140
|
+
const needsTruncation = markdown.length > extractedContent.length;
|
|
141
|
+
const result = `# ${guide.title}\n\n**Level:** ${level}\n**Detail Level:** ${detailLevel}\n**Source:** ${guide.url}\n\n${extractedContent}${needsTruncation ? '\n\n---\n*Content truncated. Use detail_level="full" for complete tutorial or visit the link above.*' : ''}`;
|
|
142
|
+
this.cache.set(cacheKey, result);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('Error fetching tutorial:', error);
|
|
147
|
+
return `# Tutorial Error\n\nUnable to fetch tutorial for "${topic}": ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
72
148
|
}
|
|
73
|
-
this.cache.setLongTerm(cacheKey, tutorial);
|
|
74
|
-
return tutorial;
|
|
75
149
|
}
|
|
76
150
|
/**
|
|
77
|
-
* Compare Spring Boot versions
|
|
151
|
+
* Compare Spring Boot versions using real release notes
|
|
78
152
|
*/
|
|
79
153
|
async compareVersions(version1, version2, focus = 'all') {
|
|
80
|
-
const cacheKey = `
|
|
154
|
+
const cacheKey = `versions:${version1}:${version2}:${focus}`;
|
|
81
155
|
const cached = this.cache.get(cacheKey);
|
|
82
156
|
if (cached)
|
|
83
157
|
return cached;
|
|
84
158
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
159
|
+
// Fetch release notes from GitHub
|
|
160
|
+
const releaseNotesUrl = `https://api.github.com/repos/spring-projects/spring-boot/releases`;
|
|
161
|
+
const response = await this.fetchWithRetry(releaseNotesUrl);
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
throw new Error(`Failed to fetch release data: ${response.status}`);
|
|
164
|
+
}
|
|
165
|
+
const releases = await response.json();
|
|
166
|
+
const release1 = releases.find((r) => r.tag_name.includes(version1));
|
|
167
|
+
const release2 = releases.find((r) => r.tag_name.includes(version2));
|
|
168
|
+
if (!release1 || !release2) {
|
|
169
|
+
return `# Version Comparison: ${version1} vs ${version2}\n\nUnable to find release information for one or both versions.\n\nAvailable versions can be found at: https://github.com/spring-projects/spring-boot/releases`;
|
|
170
|
+
}
|
|
171
|
+
const result = `# Spring Boot Version Comparison: ${version1} vs ${version2}
|
|
172
|
+
|
|
173
|
+
## Version ${version1}
|
|
174
|
+
**Released:** ${new Date(release1.published_at).toLocaleDateString()}
|
|
175
|
+
**Release Notes:** ${release1.html_url}
|
|
176
|
+
|
|
177
|
+
${release1.body.substring(0, 1000)}...
|
|
178
|
+
|
|
179
|
+
## Version ${version2}
|
|
180
|
+
**Released:** ${new Date(release2.published_at).toLocaleDateString()}
|
|
181
|
+
**Release Notes:** ${release2.html_url}
|
|
182
|
+
|
|
183
|
+
${release2.body.substring(0, 1000)}...
|
|
184
|
+
|
|
185
|
+
## Migration Recommendations
|
|
186
|
+
1. Review the full release notes at the URLs above
|
|
187
|
+
2. Check for breaking changes in your dependencies
|
|
188
|
+
3. Update your Spring Boot version gradually
|
|
189
|
+
4. Test thoroughly in a staging environment
|
|
190
|
+
|
|
191
|
+
For detailed migration guides, visit: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide`;
|
|
192
|
+
this.cache.setLongTerm(cacheKey, result);
|
|
193
|
+
return result;
|
|
88
194
|
}
|
|
89
195
|
catch (error) {
|
|
90
196
|
console.error('Error comparing versions:', error);
|
|
91
|
-
|
|
197
|
+
return `# Version Comparison Error\n\nUnable to compare versions ${version1} and ${version2}: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
92
198
|
}
|
|
93
199
|
}
|
|
94
200
|
/**
|
|
95
|
-
* Get best practices
|
|
201
|
+
* Get best practices from official Spring documentation
|
|
96
202
|
*/
|
|
97
203
|
async getBestPractices(category, experienceLevel = 'intermediate') {
|
|
98
204
|
const cacheKey = `practices:${category}:${experienceLevel}`;
|
|
99
205
|
const cached = this.cache.get(cacheKey);
|
|
100
206
|
if (cached)
|
|
101
207
|
return cached;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
208
|
+
try {
|
|
209
|
+
// Map categories to Spring Boot documentation sections
|
|
210
|
+
const docSections = {
|
|
211
|
+
'architecture': 'spring-boot-features.html#boot-features-spring-application',
|
|
212
|
+
'performance': 'actuator.html#actuator.metrics',
|
|
213
|
+
'security': 'spring-security.html',
|
|
214
|
+
'testing': 'spring-boot-features.html#boot-features-testing',
|
|
215
|
+
'configuration': 'spring-boot-features.html#boot-features-external-config',
|
|
216
|
+
'deployment': 'deployment.html'
|
|
217
|
+
};
|
|
218
|
+
const section = docSections[category];
|
|
219
|
+
if (!section) {
|
|
220
|
+
const availableCategories = Object.keys(docSections).join(', ');
|
|
221
|
+
return `# Best Practices Not Found\n\nCategory "${category}" not available.\n\n## Available Categories:\n${availableCategories}\n\nPlease use one of the available categories.`;
|
|
222
|
+
}
|
|
223
|
+
// Fetch from Spring Boot reference documentation
|
|
224
|
+
const docUrl = `${this.baseUrl}/spring-boot/docs/current/reference/html/${section}`;
|
|
225
|
+
const response = await this.fetchWithRetry(docUrl);
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`Failed to fetch documentation: ${response.status}`);
|
|
228
|
+
}
|
|
229
|
+
const html = await response.text();
|
|
230
|
+
const $ = cheerio.load(html);
|
|
231
|
+
// Extract relevant content
|
|
232
|
+
const content = $('.content, .sect1, .chapter, main').first();
|
|
233
|
+
if (content.length === 0) {
|
|
234
|
+
throw new Error('No content found in documentation');
|
|
235
|
+
}
|
|
236
|
+
// Convert to markdown and format
|
|
237
|
+
const markdown = this.turndownService.turndown(content.html() || '');
|
|
238
|
+
const result = `# Spring Boot ${category.charAt(0).toUpperCase() + category.slice(1)} Best Practices
|
|
107
239
|
|
|
108
|
-
|
|
240
|
+
**Experience Level:** ${experienceLevel}
|
|
241
|
+
**Source:** ${docUrl}
|
|
109
242
|
|
|
110
|
-
|
|
111
|
-
${availableCategories}
|
|
243
|
+
${markdown.substring(0, 1000)}...
|
|
112
244
|
|
|
113
|
-
|
|
245
|
+
For complete documentation, visit: ${docUrl}`;
|
|
246
|
+
this.cache.setLongTerm(cacheKey, result);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
console.error('Error fetching best practices:', error);
|
|
251
|
+
return `# Best Practices Error\n\nUnable to fetch best practices for "${category}": ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
114
252
|
}
|
|
115
|
-
const result = this.formatBestPractices(categoryPractices, category, experienceLevel);
|
|
116
|
-
this.cache.setLongTerm(cacheKey, result);
|
|
117
|
-
return result;
|
|
118
253
|
}
|
|
119
254
|
/**
|
|
120
|
-
* Diagnose Spring
|
|
255
|
+
* Diagnose issues using Spring Boot documentation
|
|
121
256
|
*/
|
|
122
257
|
async diagnoseIssues(errorMessage, component, stackTrace) {
|
|
123
|
-
const cacheKey = `
|
|
258
|
+
const cacheKey = `diagnosis:${errorMessage.substring(0, 50)}`;
|
|
124
259
|
const cached = this.cache.get(cacheKey);
|
|
125
260
|
if (cached)
|
|
126
261
|
return cached;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
262
|
+
try {
|
|
263
|
+
// Search for the error in Spring Boot documentation
|
|
264
|
+
const searchQuery = errorMessage.split(' ').slice(0, 3).join(' ');
|
|
265
|
+
const docs = await this.searchDocumentation(searchQuery, 3);
|
|
266
|
+
let result = `# Spring Boot Issue Diagnosis\n\n**Error:** ${errorMessage}\n`;
|
|
267
|
+
if (component) {
|
|
268
|
+
result += `**Component:** ${component}\n`;
|
|
269
|
+
}
|
|
270
|
+
result += `\n## Relevant Documentation\n\n`;
|
|
271
|
+
if (docs.length > 0) {
|
|
272
|
+
docs.forEach((doc, index) => {
|
|
273
|
+
result += `${index + 1}. **${doc.title}**\n ${doc.url}\n\n`;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
result += `No specific documentation found for this error.\n\n`;
|
|
278
|
+
}
|
|
279
|
+
result += `## General Troubleshooting Steps\n\n`;
|
|
280
|
+
result += `1. Check the Spring Boot documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/\n`;
|
|
281
|
+
result += `2. Search Spring Boot issues: https://github.com/spring-projects/spring-boot/issues\n`;
|
|
282
|
+
result += `3. Enable debug logging: \`logging.level.org.springframework=DEBUG\`\n`;
|
|
283
|
+
result += `4. Check actuator health endpoint: \`/actuator/health\`\n\n`;
|
|
284
|
+
if (stackTrace) {
|
|
285
|
+
result += `## Stack Trace Analysis\n\nFor detailed stack trace analysis, consider:\n`;
|
|
286
|
+
result += `- Looking for the root cause in the stack trace\n`;
|
|
287
|
+
result += `- Checking for configuration issues\n`;
|
|
288
|
+
result += `- Verifying dependency versions\n\n`;
|
|
289
|
+
}
|
|
290
|
+
this.cache.set(cacheKey, result);
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
console.error('Error diagnosing issue:', error);
|
|
295
|
+
return `# Diagnosis Error\n\nUnable to diagnose issue: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
296
|
+
}
|
|
130
297
|
}
|
|
131
|
-
//
|
|
298
|
+
// Real implementation methods (no mock data)
|
|
132
299
|
async searchProjects(query, limit) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
300
|
+
try {
|
|
301
|
+
const response = await this.fetchWithRetry(this.springProjectsUrl);
|
|
302
|
+
if (!response.ok) {
|
|
303
|
+
throw new Error('Failed to fetch Spring projects');
|
|
304
|
+
}
|
|
305
|
+
const html = await response.text();
|
|
306
|
+
const $ = cheerio.load(html);
|
|
307
|
+
const projects = [];
|
|
308
|
+
$('.project-list .project, .project-item, .card, .project-card').each((index, element) => {
|
|
309
|
+
const $project = $(element);
|
|
310
|
+
const name = $project.find('h2, h3, .title, .project-title, .card-title').first().text().trim();
|
|
311
|
+
const description = $project.find('p, .description, .summary, .card-text').first().text().trim();
|
|
312
|
+
const link = $project.find('a').first().attr('href');
|
|
313
|
+
if (name && description) {
|
|
314
|
+
const url = link?.startsWith('http') ? link : `https://spring.io${link}`;
|
|
315
|
+
if (name.toLowerCase().includes(query.toLowerCase()) ||
|
|
316
|
+
description.toLowerCase().includes(query.toLowerCase())) {
|
|
317
|
+
projects.push({ name, description, url, type: 'project' });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return projects.slice(0, limit);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error('Error searching projects:', error);
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
143
327
|
}
|
|
144
328
|
async searchGuides(query, limit) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
329
|
+
try {
|
|
330
|
+
const response = await this.fetchWithRetry(this.springGuideUrl);
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
throw new Error('Failed to fetch Spring guides');
|
|
333
|
+
}
|
|
334
|
+
const html = await response.text();
|
|
335
|
+
const $ = cheerio.load(html);
|
|
336
|
+
const guides = [];
|
|
337
|
+
$('.guide-item, .card, .guide-card, .list-item').each((index, element) => {
|
|
338
|
+
const $guide = $(element);
|
|
339
|
+
const title = $guide.find('h2, h3, .title, .guide-title, .card-title, a').first().text().trim();
|
|
340
|
+
const description = $guide.find('p, .description, .summary, .card-text').first().text().trim();
|
|
341
|
+
const link = $guide.find('a').first().attr('href');
|
|
342
|
+
const type = $guide.find('.badge, .label, .type').first().text().trim() || 'Guide';
|
|
343
|
+
if (title) {
|
|
344
|
+
const url = link?.startsWith('http') ? link : `https://spring.io${link}`;
|
|
345
|
+
if (title.toLowerCase().includes(query.toLowerCase()) ||
|
|
346
|
+
description.toLowerCase().includes(query.toLowerCase())) {
|
|
347
|
+
guides.push({ title, description: description || 'Spring Boot guide', url, type });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return guides.slice(0, limit);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.error('Error searching guides:', error);
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
153
357
|
}
|
|
154
358
|
async searchDocumentation(query, limit) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
359
|
+
try {
|
|
360
|
+
const bootDocsUrl = `${this.baseUrl}/spring-boot/docs/current/reference/html/`;
|
|
361
|
+
const response = await this.fetchWithRetry(bootDocsUrl);
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
throw new Error('Failed to fetch Spring Boot documentation');
|
|
364
|
+
}
|
|
365
|
+
const html = await response.text();
|
|
366
|
+
const $ = cheerio.load(html);
|
|
367
|
+
const docs = [];
|
|
368
|
+
$('nav a, .toc a, .nav-link, .chapter a').each((index, element) => {
|
|
369
|
+
const $link = $(element);
|
|
370
|
+
const title = $link.text().trim();
|
|
371
|
+
const href = $link.attr('href');
|
|
372
|
+
if (title && href && title.toLowerCase().includes(query.toLowerCase())) {
|
|
373
|
+
const url = href.startsWith('http') ? href : `${bootDocsUrl}${href}`;
|
|
374
|
+
docs.push({ title: `Spring Boot: ${title}`, url, type: 'Reference Documentation' });
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
return docs.slice(0, limit);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
console.error('Error searching documentation:', error);
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
162
383
|
}
|
|
163
384
|
async searchAPI(query, limit) {
|
|
164
|
-
const
|
|
165
|
-
{ title: 'Spring Boot API Documentation', url: 'https://docs.spring.io/spring-boot/docs/current/api/',
|
|
166
|
-
{ title: 'Spring Framework API', url: 'https://docs.spring.io/spring-framework/docs/current/javadoc-api/',
|
|
385
|
+
const apis = [
|
|
386
|
+
{ title: 'Spring Boot API Documentation', url: 'https://docs.spring.io/spring-boot/docs/current/api/', keywords: ['boot', 'autoconfiguration', 'starters'] },
|
|
387
|
+
{ title: 'Spring Framework API', url: 'https://docs.spring.io/spring-framework/docs/current/javadoc-api/', keywords: ['core', 'context', 'beans', 'web'] },
|
|
388
|
+
{ title: 'Spring Security API', url: 'https://docs.spring.io/spring-security/site/docs/current/api/', keywords: ['security', 'authentication', 'config'] },
|
|
389
|
+
{ title: 'Spring Data JPA API', url: 'https://docs.spring.io/spring-data/jpa/docs/current/api/', keywords: ['jpa', 'repository', 'query'] }
|
|
167
390
|
];
|
|
168
|
-
|
|
169
|
-
|
|
391
|
+
const queryLower = query.toLowerCase();
|
|
392
|
+
return apis
|
|
393
|
+
.filter(a => a.title.toLowerCase().includes(queryLower) ||
|
|
394
|
+
a.keywords.some(keyword => keyword.toLowerCase().includes(queryLower)))
|
|
395
|
+
.map(a => ({ title: a.title, url: a.url, type: 'API Documentation' }))
|
|
170
396
|
.slice(0, limit);
|
|
171
397
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
curl https://start.spring.io/starter.zip \\
|
|
185
|
-
-d dependencies=web \\
|
|
186
|
-
-d name=my-rest-api \\
|
|
187
|
-
-d packageName=com.example.api \\
|
|
188
|
-
-o my-rest-api.zip
|
|
189
|
-
\`\`\`
|
|
190
|
-
|
|
191
|
-
## Step 2: Create a Simple Controller
|
|
192
|
-
\`\`\`java
|
|
193
|
-
@RestController
|
|
194
|
-
@RequestMapping("/api")
|
|
195
|
-
public class HelloController {
|
|
196
|
-
|
|
197
|
-
@GetMapping("/hello")
|
|
198
|
-
public String hello() {
|
|
199
|
-
return "Hello, World!";
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
@GetMapping("/hello/{name}")
|
|
203
|
-
public String helloName(@PathVariable String name) {
|
|
204
|
-
return "Hello, " + name + "!";
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
\`\`\`
|
|
208
|
-
|
|
209
|
-
## Step 3: Run the Application
|
|
210
|
-
\`\`\`bash
|
|
211
|
-
./mvnw spring-boot:run
|
|
212
|
-
\`\`\`
|
|
213
|
-
|
|
214
|
-
## Step 4: Test Your API
|
|
215
|
-
\`\`\`bash
|
|
216
|
-
curl http://localhost:8080/api/hello
|
|
217
|
-
curl http://localhost:8080/api/hello/John
|
|
218
|
-
\`\`\`
|
|
219
|
-
|
|
220
|
-
## Next Steps
|
|
221
|
-
- Add request/response DTOs
|
|
222
|
-
- Implement POST, PUT, DELETE methods
|
|
223
|
-
- Add input validation
|
|
224
|
-
- Handle exceptions properly`,
|
|
225
|
-
intermediate: `# Advanced REST API Development with Spring Boot
|
|
226
|
-
|
|
227
|
-
## Data Transfer Objects (DTOs)
|
|
228
|
-
\`\`\`java
|
|
229
|
-
public class UserDto {
|
|
230
|
-
@NotBlank
|
|
231
|
-
private String name;
|
|
232
|
-
|
|
233
|
-
@Email
|
|
234
|
-
private String email;
|
|
235
|
-
|
|
236
|
-
// getters and setters
|
|
237
|
-
}
|
|
238
|
-
\`\`\`
|
|
239
|
-
|
|
240
|
-
## Service Layer
|
|
241
|
-
\`\`\`java
|
|
242
|
-
@Service
|
|
243
|
-
public class UserService {
|
|
244
|
-
|
|
245
|
-
@Autowired
|
|
246
|
-
private UserRepository userRepository;
|
|
247
|
-
|
|
248
|
-
public UserDto createUser(UserDto userDto) {
|
|
249
|
-
User user = mapToEntity(userDto);
|
|
250
|
-
User savedUser = userRepository.save(user);
|
|
251
|
-
return mapToDto(savedUser);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
\`\`\`
|
|
255
|
-
|
|
256
|
-
## Exception Handling
|
|
257
|
-
\`\`\`java
|
|
258
|
-
@ControllerAdvice
|
|
259
|
-
public class GlobalExceptionHandler {
|
|
260
|
-
|
|
261
|
-
@ExceptionHandler(ValidationException.class)
|
|
262
|
-
public ResponseEntity<ErrorResponse> handleValidation(ValidationException ex) {
|
|
263
|
-
return ResponseEntity.badRequest()
|
|
264
|
-
.body(new ErrorResponse("Validation failed", ex.getMessage()));
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
\`\`\`
|
|
268
|
-
|
|
269
|
-
## Testing
|
|
270
|
-
\`\`\`java
|
|
271
|
-
@SpringBootTest
|
|
272
|
-
@AutoConfigureTestDatabase
|
|
273
|
-
class UserControllerTest {
|
|
274
|
-
|
|
275
|
-
@Autowired
|
|
276
|
-
private TestRestTemplate restTemplate;
|
|
277
|
-
|
|
278
|
-
@Test
|
|
279
|
-
void shouldCreateUser() {
|
|
280
|
-
UserDto user = new UserDto("John", "john@example.com");
|
|
281
|
-
ResponseEntity<UserDto> response = restTemplate.postForEntity(
|
|
282
|
-
"/api/users", user, UserDto.class);
|
|
283
|
-
|
|
284
|
-
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
\`\`\``,
|
|
288
|
-
advanced: `# Production-Ready REST API with Spring Boot
|
|
289
|
-
|
|
290
|
-
## Advanced Features Implementation
|
|
291
|
-
|
|
292
|
-
### 1. API Versioning
|
|
293
|
-
\`\`\`java
|
|
294
|
-
@RestController
|
|
295
|
-
@RequestMapping("/api/v1/users")
|
|
296
|
-
public class UserControllerV1 {
|
|
297
|
-
// Version 1 implementation
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
@RestController
|
|
301
|
-
@RequestMapping("/api/v2/users")
|
|
302
|
-
public class UserControllerV2 {
|
|
303
|
-
// Version 2 with breaking changes
|
|
304
|
-
}
|
|
305
|
-
\`\`\`
|
|
306
|
-
|
|
307
|
-
### 2. HATEOAS Implementation
|
|
308
|
-
\`\`\`java
|
|
309
|
-
@GetMapping("/{id}")
|
|
310
|
-
public EntityModel<UserDto> getUser(@PathVariable Long id) {
|
|
311
|
-
UserDto user = userService.findById(id);
|
|
312
|
-
return EntityModel.of(user)
|
|
313
|
-
.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel())
|
|
314
|
-
.add(linkTo(UserController.class).withRel("users"));
|
|
315
|
-
}
|
|
316
|
-
\`\`\`
|
|
317
|
-
|
|
318
|
-
### 3. Advanced Security
|
|
319
|
-
\`\`\`java
|
|
320
|
-
@Configuration
|
|
321
|
-
@EnableWebSecurity
|
|
322
|
-
public class SecurityConfig {
|
|
323
|
-
|
|
324
|
-
@Bean
|
|
325
|
-
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
326
|
-
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()))
|
|
327
|
-
.authorizeHttpRequests(authz -> authz
|
|
328
|
-
.requestMatchers("/api/public/**").permitAll()
|
|
329
|
-
.requestMatchers(HttpMethod.GET, "/api/users").hasRole("USER")
|
|
330
|
-
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
|
331
|
-
.anyRequest().authenticated()
|
|
332
|
-
);
|
|
333
|
-
return http.build();
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
\`\`\`
|
|
337
|
-
|
|
338
|
-
### 4. Metrics and Monitoring
|
|
339
|
-
\`\`\`java
|
|
340
|
-
@Component
|
|
341
|
-
public class ApiMetrics {
|
|
342
|
-
|
|
343
|
-
private final MeterRegistry meterRegistry;
|
|
344
|
-
private final Counter apiCallCounter;
|
|
345
|
-
|
|
346
|
-
public ApiMetrics(MeterRegistry meterRegistry) {
|
|
347
|
-
this.meterRegistry = meterRegistry;
|
|
348
|
-
this.apiCallCounter = Counter.builder("api.calls")
|
|
349
|
-
.description("Number of API calls")
|
|
350
|
-
.register(meterRegistry);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
\`\`\`
|
|
354
|
-
|
|
355
|
-
### 5. Rate Limiting
|
|
356
|
-
\`\`\`java
|
|
357
|
-
@Component
|
|
358
|
-
public class RateLimitingFilter implements Filter {
|
|
359
|
-
|
|
360
|
-
private final RedisTemplate<String, String> redisTemplate;
|
|
361
|
-
|
|
362
|
-
@Override
|
|
363
|
-
public void doFilter(ServletRequest request, ServletResponse response,
|
|
364
|
-
FilterChain chain) throws IOException, ServletException {
|
|
365
|
-
|
|
366
|
-
String clientId = getClientId(request);
|
|
367
|
-
if (isRateLimited(clientId)) {
|
|
368
|
-
((HttpServletResponse) response).setStatus(429);
|
|
369
|
-
return;
|
|
398
|
+
formatEcosystemResults(results) {
|
|
399
|
+
let output = `# Spring Ecosystem Search Results\n\n`;
|
|
400
|
+
output += `**Query:** ${results.query}\n`;
|
|
401
|
+
output += `**Scope:** ${results.scope}\n`;
|
|
402
|
+
output += `**Total Results:** ${results.totalResults}\n\n`;
|
|
403
|
+
if (results.categories.projects?.length > 0) {
|
|
404
|
+
output += `## Projects\n\n`;
|
|
405
|
+
results.categories.projects.forEach((project, index) => {
|
|
406
|
+
output += `${index + 1}. **${project.name}**\n`;
|
|
407
|
+
output += ` ${project.description}\n`;
|
|
408
|
+
output += ` URL: ${project.url}\n\n`;
|
|
409
|
+
});
|
|
370
410
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
async generateVersionComparison(version1, version2, focus) {
|
|
381
|
-
// This would fetch actual version data in a real implementation
|
|
382
|
-
return `# Spring Boot Version Comparison: ${version1} vs ${version2}
|
|
383
|
-
|
|
384
|
-
## Overview
|
|
385
|
-
Comparing Spring Boot ${version1} with ${version2}
|
|
386
|
-
|
|
387
|
-
## Major Changes
|
|
388
|
-
${focus === 'all' || focus === 'breaking-changes' ? `
|
|
389
|
-
### Breaking Changes
|
|
390
|
-
- Java version requirements updated
|
|
391
|
-
- Configuration property changes
|
|
392
|
-
- Deprecated APIs removed
|
|
393
|
-
` : ''}
|
|
394
|
-
|
|
395
|
-
${focus === 'all' || focus === 'new-features' ? `
|
|
396
|
-
### New Features
|
|
397
|
-
- Enhanced auto-configuration
|
|
398
|
-
- New starters available
|
|
399
|
-
- Improved monitoring capabilities
|
|
400
|
-
` : ''}
|
|
401
|
-
|
|
402
|
-
${focus === 'all' || focus === 'deprecations' ? `
|
|
403
|
-
### Deprecations
|
|
404
|
-
- Legacy configuration support
|
|
405
|
-
- Outdated dependencies
|
|
406
|
-
- Old API patterns
|
|
407
|
-
` : ''}
|
|
408
|
-
|
|
409
|
-
## Migration Guide
|
|
410
|
-
1. Update Java version if required
|
|
411
|
-
2. Update dependency versions
|
|
412
|
-
3. Review configuration changes
|
|
413
|
-
4. Test thoroughly
|
|
414
|
-
|
|
415
|
-
## Recommendation
|
|
416
|
-
${this.getUpgradeRecommendation(version1, version2)}`;
|
|
417
|
-
}
|
|
418
|
-
getBestPracticesDatabase() {
|
|
419
|
-
return {
|
|
420
|
-
architecture: {
|
|
421
|
-
title: 'Spring Boot Architecture Best Practices',
|
|
422
|
-
practices: [
|
|
423
|
-
{
|
|
424
|
-
title: 'Layer Separation',
|
|
425
|
-
description: 'Maintain clear separation between presentation, business, and data layers',
|
|
426
|
-
examples: ['Controller → Service → Repository pattern', 'Use DTOs for data transfer'],
|
|
427
|
-
level: ['beginner', 'intermediate', 'expert']
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
title: 'Dependency Injection',
|
|
431
|
-
description: 'Use constructor injection and avoid field injection',
|
|
432
|
-
examples: ['@Autowired on constructors', 'Final fields for dependencies'],
|
|
433
|
-
level: ['intermediate', 'expert']
|
|
434
|
-
}
|
|
435
|
-
]
|
|
436
|
-
},
|
|
437
|
-
performance: {
|
|
438
|
-
title: 'Performance Optimization',
|
|
439
|
-
practices: [
|
|
440
|
-
{
|
|
441
|
-
title: 'Lazy Loading',
|
|
442
|
-
description: 'Use lazy loading for JPA relationships',
|
|
443
|
-
examples: ['@OneToMany(fetch = FetchType.LAZY)', 'Use @EntityGraph for specific queries'],
|
|
444
|
-
level: ['intermediate', 'expert']
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
title: 'Connection Pooling',
|
|
448
|
-
description: 'Configure proper database connection pooling',
|
|
449
|
-
examples: ['HikariCP configuration', 'Monitor connection usage'],
|
|
450
|
-
level: ['expert']
|
|
451
|
-
}
|
|
452
|
-
]
|
|
453
|
-
}
|
|
454
|
-
// Add more categories...
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
formatBestPractices(practices, category, level) {
|
|
458
|
-
const filtered = practices.practices.filter((p) => p.level.includes(level));
|
|
459
|
-
let result = `# ${practices.title}\n\n`;
|
|
460
|
-
result += `**Experience Level:** ${level}\n\n`;
|
|
461
|
-
filtered.forEach((practice, index) => {
|
|
462
|
-
result += `## ${index + 1}. ${practice.title}\n\n`;
|
|
463
|
-
result += `${practice.description}\n\n`;
|
|
464
|
-
result += `**Examples:**\n`;
|
|
465
|
-
practice.examples.forEach((example) => {
|
|
466
|
-
result += `- ${example}\n`;
|
|
411
|
+
if (results.categories.guides?.length > 0) {
|
|
412
|
+
output += `## Guides\n\n`;
|
|
413
|
+
results.categories.guides.forEach((guide, index) => {
|
|
414
|
+
output += `${index + 1}. **${guide.title}**\n`;
|
|
415
|
+
if (guide.description)
|
|
416
|
+
output += ` ${guide.description}\n`;
|
|
417
|
+
output += ` URL: ${guide.url}\n\n`;
|
|
467
418
|
});
|
|
468
|
-
result += '\n';
|
|
469
|
-
});
|
|
470
|
-
return result;
|
|
471
|
-
}
|
|
472
|
-
performIssueDiagnosis(errorMessage, component, stackTrace) {
|
|
473
|
-
const commonIssues = {
|
|
474
|
-
'Port 8080 was already in use': {
|
|
475
|
-
cause: 'Another application is using the default Spring Boot port',
|
|
476
|
-
solutions: [
|
|
477
|
-
'Change port in application.properties: server.port=8081',
|
|
478
|
-
'Kill the process using port 8080',
|
|
479
|
-
'Use a different port for your application'
|
|
480
|
-
]
|
|
481
|
-
},
|
|
482
|
-
'Failed to configure a DataSource': {
|
|
483
|
-
cause: 'Database configuration is missing or incorrect',
|
|
484
|
-
solutions: [
|
|
485
|
-
'Add database dependency to pom.xml/build.gradle',
|
|
486
|
-
'Configure datasource properties in application.properties',
|
|
487
|
-
'Use @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) if no database needed'
|
|
488
|
-
]
|
|
489
|
-
},
|
|
490
|
-
'NoSuchBeanDefinitionException': {
|
|
491
|
-
cause: 'Required bean is not found in the application context',
|
|
492
|
-
solutions: [
|
|
493
|
-
'Check if the class is annotated with @Component, @Service, @Repository, or @Controller',
|
|
494
|
-
'Ensure the class is in a package scanned by @ComponentScan',
|
|
495
|
-
'Verify @Autowired dependencies are correctly configured'
|
|
496
|
-
]
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
for (const [pattern, issue] of Object.entries(commonIssues)) {
|
|
500
|
-
if (errorMessage.includes(pattern)) {
|
|
501
|
-
return `# Spring Boot Issue Diagnosis
|
|
502
|
-
|
|
503
|
-
## Error Analysis
|
|
504
|
-
**Error:** ${errorMessage}
|
|
505
|
-
${component ? `**Component:** ${component}` : ''}
|
|
506
|
-
|
|
507
|
-
## Root Cause
|
|
508
|
-
${issue.cause}
|
|
509
|
-
|
|
510
|
-
## Recommended Solutions
|
|
511
|
-
${issue.solutions.map((solution, index) => `${index + 1}. ${solution}`).join('\n')}
|
|
512
|
-
|
|
513
|
-
${stackTrace ? `
|
|
514
|
-
## Stack Trace Analysis
|
|
515
|
-
The provided stack trace shows:
|
|
516
|
-
\`\`\`
|
|
517
|
-
${stackTrace.substring(0, 500)}${stackTrace.length > 500 ? '...' : ''}
|
|
518
|
-
\`\`\`
|
|
519
|
-
` : ''}
|
|
520
|
-
|
|
521
|
-
## Additional Resources
|
|
522
|
-
- [Spring Boot Documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/)
|
|
523
|
-
- [Common Application Properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html)`;
|
|
524
|
-
}
|
|
525
419
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
## Diagnosis
|
|
533
|
-
This appears to be a custom or less common error. Here are general troubleshooting steps:
|
|
534
|
-
|
|
535
|
-
1. **Check Logs:** Review the full stack trace for more context
|
|
536
|
-
2. **Verify Configuration:** Ensure all properties are correctly set
|
|
537
|
-
3. **Dependencies:** Check if all required dependencies are included
|
|
538
|
-
4. **Version Compatibility:** Verify Spring Boot and dependency versions are compatible
|
|
539
|
-
|
|
540
|
-
## Recommended Actions
|
|
541
|
-
1. Enable debug logging: \`logging.level.org.springframework=DEBUG\`
|
|
542
|
-
2. Check Spring Boot documentation for the specific component
|
|
543
|
-
3. Search Spring Boot GitHub issues for similar problems
|
|
544
|
-
4. Consider posting on Stack Overflow with the full stack trace
|
|
545
|
-
|
|
546
|
-
${stackTrace ? `
|
|
547
|
-
## Stack Trace Analysis
|
|
548
|
-
\`\`\`
|
|
549
|
-
${stackTrace.substring(0, 500)}${stackTrace.length > 500 ? '...' : ''}
|
|
550
|
-
\`\`\`
|
|
551
|
-
` : ''}`;
|
|
552
|
-
}
|
|
553
|
-
getUpgradeRecommendation(version1, version2) {
|
|
554
|
-
// Simplified version comparison logic
|
|
555
|
-
const v1Major = parseInt(version1.split('.')[0]);
|
|
556
|
-
const v2Major = parseInt(version2.split('.')[0]);
|
|
557
|
-
if (v2Major > v1Major) {
|
|
558
|
-
return `🚨 Major version upgrade detected. This may include breaking changes. Plan for thorough testing and potential code modifications.`;
|
|
420
|
+
if (results.categories.documentation?.length > 0) {
|
|
421
|
+
output += `## Documentation\n\n`;
|
|
422
|
+
results.categories.documentation.forEach((doc, index) => {
|
|
423
|
+
output += `${index + 1}. **${doc.title}**\n`;
|
|
424
|
+
output += ` URL: ${doc.url}\n\n`;
|
|
425
|
+
});
|
|
559
426
|
}
|
|
560
|
-
|
|
561
|
-
|
|
427
|
+
if (results.categories.api?.length > 0) {
|
|
428
|
+
output += `## Api\n\n`;
|
|
429
|
+
results.categories.api.forEach((api, index) => {
|
|
430
|
+
output += `${index + 1}. **${api.title}**\n`;
|
|
431
|
+
output += ` URL: ${api.url}\n\n`;
|
|
432
|
+
});
|
|
562
433
|
}
|
|
434
|
+
if (results.totalResults === 0) {
|
|
435
|
+
output += `No results found for "${results.query}" in scope "${results.scope}".`;
|
|
436
|
+
}
|
|
437
|
+
return output;
|
|
563
438
|
}
|
|
564
|
-
|
|
565
|
-
let
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
439
|
+
async fetchWithRetry(url, timeout = this.REQUEST_TIMEOUT, retries = this.MAX_RETRIES) {
|
|
440
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
441
|
+
const controller = new AbortController();
|
|
442
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
443
|
+
try {
|
|
444
|
+
const response = await fetch(url, {
|
|
445
|
+
signal: controller.signal,
|
|
446
|
+
headers: {
|
|
447
|
+
'User-Agent': 'Spring-Docs-MCP/1.2.4',
|
|
448
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
449
|
+
'Accept-Language': 'en-US,en;q=0.9'
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
clearTimeout(timeoutId);
|
|
453
|
+
return response;
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
clearTimeout(timeoutId);
|
|
457
|
+
if (attempt === retries)
|
|
458
|
+
throw error;
|
|
459
|
+
console.error(`Retry ${attempt}/${retries} for ${url}:`, error instanceof Error ? error.message : 'Unknown error');
|
|
460
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
461
|
+
}
|
|
570
462
|
}
|
|
571
|
-
|
|
463
|
+
throw new Error('All retry attempts failed');
|
|
572
464
|
}
|
|
573
465
|
}
|
|
574
466
|
//# sourceMappingURL=advanced-features.js.map
|