@merlean/analyzer 2.1.0 → 2.2.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.
- package/lib/analyzer.js +423 -124
- package/package.json +1 -1
package/lib/analyzer.js
CHANGED
|
@@ -1,66 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Framework-Agnostic Codebase Scanner
|
|
3
3
|
*
|
|
4
|
-
* Scans
|
|
5
|
-
*
|
|
4
|
+
* Scans ANY codebase (frontend or backend) to extract API patterns:
|
|
5
|
+
* - Express/Fastify/Koa/Hapi route definitions
|
|
6
|
+
* - NestJS decorators (@Get, @Post, @Body, @Query, etc.)
|
|
7
|
+
* - Frontend HTTP calls (fetch, axios, HttpClient, etc.)
|
|
8
|
+
* - Swagger/OpenAPI annotations
|
|
9
|
+
* - Request body schemas and query parameters
|
|
10
|
+
* - TypeScript interfaces/DTOs
|
|
11
|
+
* - Validation schemas (Zod, Joi, class-validator)
|
|
6
12
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
13
|
+
* The goal is to understand what APIs exist, their parameters,
|
|
14
|
+
* and body structures - regardless of framework or code style.
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
const fs = require('fs');
|
|
12
18
|
const path = require('path');
|
|
13
19
|
const { glob } = require('glob');
|
|
14
20
|
|
|
15
|
-
//
|
|
16
|
-
const
|
|
21
|
+
// File patterns to scan (be inclusive)
|
|
22
|
+
const FILE_PATTERNS = [
|
|
17
23
|
'**/*.js',
|
|
18
24
|
'**/*.jsx',
|
|
19
25
|
'**/*.ts',
|
|
20
26
|
'**/*.tsx',
|
|
21
27
|
'**/*.vue',
|
|
22
28
|
'**/*.svelte',
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'**/index.js',
|
|
26
|
-
'**/*api*.js',
|
|
27
|
-
'**/*service*.js',
|
|
28
|
-
'**/*fetch*.js',
|
|
29
|
-
'**/*http*.js'
|
|
29
|
+
'**/*.mjs',
|
|
30
|
+
'**/*.cjs'
|
|
30
31
|
];
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Only ignore truly irrelevant directories
|
|
33
34
|
const IGNORE_PATTERNS = [
|
|
34
35
|
'**/node_modules/**',
|
|
35
36
|
'**/vendor/**',
|
|
36
37
|
'**/.git/**',
|
|
37
38
|
'**/dist/**',
|
|
38
39
|
'**/build/**',
|
|
40
|
+
'**/coverage/**',
|
|
39
41
|
'**/__pycache__/**',
|
|
40
42
|
'**/venv/**',
|
|
41
43
|
'**/*.min.js',
|
|
42
44
|
'**/*.map',
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
45
|
+
'**/*.d.ts', // TypeScript declaration files
|
|
46
|
+
'**/*.spec.ts', // Test files
|
|
47
|
+
'**/*.spec.js',
|
|
48
|
+
'**/*.test.ts',
|
|
49
|
+
'**/*.test.js',
|
|
50
|
+
'**/__tests__/**',
|
|
51
|
+
'**/__mocks__/**'
|
|
48
52
|
];
|
|
49
53
|
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
// Files that are highly likely to contain API definitions
|
|
55
|
+
const HIGH_PRIORITY_PATTERNS = [
|
|
56
|
+
/routes?\.ts$/i,
|
|
57
|
+
/routes?\.js$/i,
|
|
58
|
+
/router\.ts$/i,
|
|
59
|
+
/router\.js$/i,
|
|
60
|
+
/controller\.ts$/i,
|
|
61
|
+
/controller\.js$/i,
|
|
62
|
+
/\.controller\.ts$/i,
|
|
63
|
+
/\.controller\.js$/i,
|
|
64
|
+
/service\.ts$/i,
|
|
65
|
+
/service\.js$/i,
|
|
66
|
+
/\.service\.ts$/i,
|
|
67
|
+
/\.service\.js$/i,
|
|
68
|
+
/api\.ts$/i,
|
|
69
|
+
/api\.js$/i,
|
|
70
|
+
/endpoints?\.ts$/i,
|
|
71
|
+
/endpoints?\.js$/i,
|
|
72
|
+
/http\.ts$/i,
|
|
73
|
+
/http\.js$/i,
|
|
74
|
+
/client\.ts$/i,
|
|
75
|
+
/client\.js$/i,
|
|
76
|
+
/dto\.ts$/i,
|
|
77
|
+
/\.dto\.ts$/i,
|
|
78
|
+
/interfaces?\.ts$/i,
|
|
79
|
+
/types?\.ts$/i,
|
|
80
|
+
/schema\.ts$/i,
|
|
81
|
+
/schemas?\.ts$/i,
|
|
82
|
+
/validation\.ts$/i
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Medium priority - might contain API patterns
|
|
86
|
+
const MEDIUM_PRIORITY_PATTERNS = [
|
|
87
|
+
/index\.ts$/i,
|
|
88
|
+
/index\.js$/i,
|
|
89
|
+
/app\.ts$/i,
|
|
90
|
+
/app\.js$/i,
|
|
91
|
+
/main\.ts$/i,
|
|
92
|
+
/main\.js$/i,
|
|
93
|
+
/server\.ts$/i,
|
|
94
|
+
/server\.js$/i
|
|
54
95
|
];
|
|
55
96
|
|
|
56
97
|
/**
|
|
57
|
-
* Scan codebase and collect
|
|
98
|
+
* Scan codebase and collect API patterns from any framework
|
|
58
99
|
*/
|
|
59
100
|
async function scanCodebase(codebasePath) {
|
|
60
|
-
console.log(' Scanning
|
|
101
|
+
console.log(' Scanning files...');
|
|
61
102
|
|
|
62
|
-
// Get
|
|
63
|
-
const files = await glob(
|
|
103
|
+
// Get all matching files
|
|
104
|
+
const files = await glob(FILE_PATTERNS, {
|
|
64
105
|
cwd: codebasePath,
|
|
65
106
|
ignore: IGNORE_PATTERNS,
|
|
66
107
|
absolute: true
|
|
@@ -68,23 +109,34 @@ async function scanCodebase(codebasePath) {
|
|
|
68
109
|
|
|
69
110
|
console.log(` Found ${files.length} files`);
|
|
70
111
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
112
|
+
// Categorize and prioritize files
|
|
113
|
+
const { highPriority, mediumPriority, other } = categorizeFiles(files, codebasePath);
|
|
114
|
+
|
|
115
|
+
console.log(` High priority: ${highPriority.length}, Medium: ${mediumPriority.length}, Other: ${other.length}`);
|
|
116
|
+
|
|
117
|
+
// Analyze high priority files first (routes, controllers, services, DTOs)
|
|
118
|
+
// Then medium priority, then scan others for API patterns
|
|
119
|
+
const filesToAnalyze = [
|
|
120
|
+
...highPriority,
|
|
121
|
+
...mediumPriority.slice(0, 20),
|
|
122
|
+
...other.slice(0, 50)
|
|
123
|
+
];
|
|
74
124
|
|
|
75
|
-
console.log(` Analyzing ${filesToAnalyze.length}
|
|
125
|
+
console.log(` Analyzing ${filesToAnalyze.length} files for API patterns...`);
|
|
76
126
|
|
|
77
|
-
// Read and extract API patterns from files
|
|
78
127
|
const fileContents = [];
|
|
128
|
+
let filesWithPatterns = 0;
|
|
129
|
+
|
|
79
130
|
for (const file of filesToAnalyze) {
|
|
80
131
|
try {
|
|
81
132
|
const content = fs.readFileSync(file, 'utf-8');
|
|
82
133
|
const relativePath = path.relative(codebasePath, file);
|
|
83
134
|
|
|
84
|
-
// Extract API
|
|
85
|
-
const extracted = extractApiPatterns(content, relativePath);
|
|
135
|
+
// Extract API patterns from the file
|
|
136
|
+
const extracted = extractApiPatterns(content, relativePath, file);
|
|
86
137
|
|
|
87
|
-
if (extracted.
|
|
138
|
+
if (extracted.hasApiPatterns) {
|
|
139
|
+
filesWithPatterns++;
|
|
88
140
|
fileContents.push({
|
|
89
141
|
path: relativePath,
|
|
90
142
|
content: extracted.content
|
|
@@ -95,84 +147,364 @@ async function scanCodebase(codebasePath) {
|
|
|
95
147
|
}
|
|
96
148
|
}
|
|
97
149
|
|
|
98
|
-
console.log(` Found API patterns in ${
|
|
150
|
+
console.log(` Found API patterns in ${filesWithPatterns} files`);
|
|
99
151
|
|
|
100
152
|
return fileContents;
|
|
101
153
|
}
|
|
102
154
|
|
|
103
155
|
/**
|
|
104
|
-
*
|
|
156
|
+
* Categorize files by priority
|
|
157
|
+
*/
|
|
158
|
+
function categorizeFiles(files, basePath) {
|
|
159
|
+
const highPriority = [];
|
|
160
|
+
const mediumPriority = [];
|
|
161
|
+
const other = [];
|
|
162
|
+
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
const relativePath = path.relative(basePath, file);
|
|
165
|
+
|
|
166
|
+
if (HIGH_PRIORITY_PATTERNS.some(p => p.test(relativePath))) {
|
|
167
|
+
highPriority.push(file);
|
|
168
|
+
} else if (MEDIUM_PRIORITY_PATTERNS.some(p => p.test(relativePath))) {
|
|
169
|
+
mediumPriority.push(file);
|
|
170
|
+
} else {
|
|
171
|
+
other.push(file);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { highPriority, mediumPriority, other };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Extract Swagger/OpenAPI documentation blocks
|
|
105
180
|
*/
|
|
106
|
-
function
|
|
107
|
-
const
|
|
181
|
+
function extractSwaggerBlocks(content) {
|
|
182
|
+
const swaggerBlocks = [];
|
|
183
|
+
|
|
184
|
+
// Match JSDoc blocks with @swagger
|
|
185
|
+
const swaggerRegex = /\/\*\*[\s\S]*?@swagger[\s\S]*?\*\//g;
|
|
186
|
+
let match;
|
|
187
|
+
|
|
188
|
+
while ((match = swaggerRegex.exec(content)) !== null) {
|
|
189
|
+
swaggerBlocks.push(match[0]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return swaggerBlocks;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Extract TypeScript interfaces and types that look like DTOs/schemas
|
|
197
|
+
*/
|
|
198
|
+
function extractTypeDefinitions(content, filePath) {
|
|
199
|
+
const typeBlocks = [];
|
|
108
200
|
const lines = content.split('\n');
|
|
109
201
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
202
|
+
// Look for interfaces and types that might be request/response schemas
|
|
203
|
+
const typeKeywords = [
|
|
204
|
+
/interface\s+\w*(Request|Response|Dto|Body|Query|Params|Payload|Input|Output)\w*\s*\{/i,
|
|
205
|
+
/type\s+\w*(Request|Response|Dto|Body|Query|Params|Payload|Input|Output)\w*\s*=/i,
|
|
206
|
+
/interface\s+\w+\s*\{/, // Any interface in DTO/schema files
|
|
207
|
+
/type\s+\w+\s*=/ // Any type in DTO/schema files
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
// Only extract type definitions from files that look like DTOs/types
|
|
211
|
+
const isDtoFile = /dto|interface|type|schema|model/i.test(filePath);
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < lines.length; i++) {
|
|
214
|
+
const line = lines[i];
|
|
123
215
|
|
|
124
|
-
|
|
125
|
-
{ regex: /['"`](\/api\/[^'"`\s]+)['"`]/g, type: 'api-url' },
|
|
126
|
-
{ regex: /['"`](https?:\/\/[^'"`\s]*\/api[^'"`\s]*)['"`]/g, type: 'full-url' },
|
|
216
|
+
const isTypeDefinition = typeKeywords.some(regex => regex.test(line));
|
|
127
217
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
218
|
+
if (isTypeDefinition || (isDtoFile && /^(export\s+)?(interface|type)\s+/.test(line))) {
|
|
219
|
+
// Find the complete type block (until closing brace at same indent level)
|
|
220
|
+
let braceCount = 0;
|
|
221
|
+
let started = false;
|
|
222
|
+
let endLine = i;
|
|
223
|
+
|
|
224
|
+
for (let j = i; j < lines.length && j < i + 50; j++) {
|
|
225
|
+
const l = lines[j];
|
|
226
|
+
for (const char of l) {
|
|
227
|
+
if (char === '{') {
|
|
228
|
+
braceCount++;
|
|
229
|
+
started = true;
|
|
230
|
+
} else if (char === '}') {
|
|
231
|
+
braceCount--;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
endLine = j;
|
|
235
|
+
if (started && braceCount === 0) break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const block = lines.slice(i, endLine + 1)
|
|
239
|
+
.map((l, idx) => `${i + idx + 1}: ${l}`)
|
|
240
|
+
.join('\n');
|
|
241
|
+
|
|
242
|
+
typeBlocks.push(block);
|
|
243
|
+
i = endLine; // Skip processed lines
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return typeBlocks;
|
|
248
|
+
}
|
|
131
249
|
|
|
132
|
-
|
|
133
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Extract request body and query parameter patterns
|
|
252
|
+
*/
|
|
253
|
+
function extractRequestPatterns(content) {
|
|
254
|
+
const patterns = [];
|
|
255
|
+
const lines = content.split('\n');
|
|
134
256
|
|
|
135
|
-
// FIRST: Extract API base URL definitions (critical for resolving paths)
|
|
136
|
-
const baseUrlDefinitions = [];
|
|
137
257
|
for (let i = 0; i < lines.length; i++) {
|
|
138
258
|
const line = lines[i];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
259
|
+
|
|
260
|
+
// Destructuring patterns for req.body, req.query, req.params
|
|
261
|
+
if (/(?:const|let|var)\s*\{[^}]+\}\s*=\s*req\.(body|query|params)/i.test(line) ||
|
|
262
|
+
/req\.(body|query|params)\s*[;.]/.test(line)) {
|
|
263
|
+
|
|
264
|
+
// Get context around it
|
|
265
|
+
const startLine = Math.max(0, i - 2);
|
|
266
|
+
const endLine = Math.min(lines.length - 1, i + 5);
|
|
267
|
+
|
|
268
|
+
const block = lines.slice(startLine, endLine + 1)
|
|
269
|
+
.map((l, idx) => `${startLine + idx + 1}: ${l}`)
|
|
270
|
+
.join('\n');
|
|
271
|
+
|
|
272
|
+
patterns.push(`// Request parameter extraction:\n${block}`);
|
|
273
|
+
i = endLine;
|
|
142
274
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
275
|
+
|
|
276
|
+
// NestJS decorators: @Body(), @Query(), @Param()
|
|
277
|
+
if (/@(Body|Query|Param|Headers)\s*\(/i.test(line)) {
|
|
278
|
+
const startLine = Math.max(0, i - 1);
|
|
279
|
+
const endLine = Math.min(lines.length - 1, i + 3);
|
|
280
|
+
|
|
281
|
+
const block = lines.slice(startLine, endLine + 1)
|
|
282
|
+
.map((l, idx) => `${startLine + idx + 1}: ${l}`)
|
|
283
|
+
.join('\n');
|
|
284
|
+
|
|
285
|
+
patterns.push(`// NestJS parameter decorator:\n${block}`);
|
|
286
|
+
i = endLine;
|
|
146
287
|
}
|
|
288
|
+
|
|
289
|
+
// Validation schemas: Joi, Zod, class-validator
|
|
290
|
+
if (/Joi\.(object|string|number|array|boolean)\s*\(/i.test(line) ||
|
|
291
|
+
/z\.(object|string|number|array|boolean)\s*\(/i.test(line) ||
|
|
292
|
+
/@(IsString|IsNumber|IsArray|IsBoolean|IsOptional|ValidateNested)/i.test(line)) {
|
|
293
|
+
|
|
294
|
+
const startLine = Math.max(0, i - 2);
|
|
295
|
+
const endLine = Math.min(lines.length - 1, i + 10);
|
|
296
|
+
|
|
297
|
+
const block = lines.slice(startLine, endLine + 1)
|
|
298
|
+
.map((l, idx) => `${startLine + idx + 1}: ${l}`)
|
|
299
|
+
.join('\n');
|
|
300
|
+
|
|
301
|
+
patterns.push(`// Validation schema:\n${block}`);
|
|
302
|
+
i = endLine;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return patterns;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Extract API patterns from file content - framework agnostic
|
|
311
|
+
*/
|
|
312
|
+
function extractApiPatterns(content, filePath, absolutePath) {
|
|
313
|
+
const lines = content.split('\n');
|
|
314
|
+
let hasApiPatterns = false;
|
|
315
|
+
const extractedBlocks = [];
|
|
316
|
+
|
|
317
|
+
// Check if this is a route/controller file - if so, include more context
|
|
318
|
+
const isRouteFile = HIGH_PRIORITY_PATTERNS.some(p => p.test(filePath));
|
|
319
|
+
const isDtoFile = /dto|interface|types?|schema/i.test(filePath);
|
|
320
|
+
|
|
321
|
+
// ============================================
|
|
322
|
+
// PATTERN 0: Swagger/OpenAPI documentation
|
|
323
|
+
// ============================================
|
|
324
|
+
const swaggerBlocks = extractSwaggerBlocks(content);
|
|
325
|
+
if (swaggerBlocks.length > 0) {
|
|
326
|
+
hasApiPatterns = true;
|
|
327
|
+
extractedBlocks.push(`// Swagger/OpenAPI documentation found:\n${swaggerBlocks.join('\n\n')}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ============================================
|
|
331
|
+
// PATTERN 1: TypeScript interfaces/types (DTOs, schemas)
|
|
332
|
+
// ============================================
|
|
333
|
+
const typeBlocks = extractTypeDefinitions(content, filePath);
|
|
334
|
+
if (typeBlocks.length > 0) {
|
|
335
|
+
hasApiPatterns = true;
|
|
336
|
+
extractedBlocks.push(`// TypeScript type definitions:\n${typeBlocks.join('\n\n')}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ============================================
|
|
340
|
+
// PATTERN 2: Request body/query extraction
|
|
341
|
+
// ============================================
|
|
342
|
+
const requestPatterns = extractRequestPatterns(content);
|
|
343
|
+
if (requestPatterns.length > 0) {
|
|
344
|
+
hasApiPatterns = true;
|
|
345
|
+
extractedBlocks.push(requestPatterns.join('\n\n'));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============================================
|
|
349
|
+
// PATTERN 3: Express/Koa/Fastify Router definitions
|
|
350
|
+
// ============================================
|
|
351
|
+
const routerPatterns = [
|
|
352
|
+
// Express Router: router.get('/path', handler)
|
|
353
|
+
/\.(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
354
|
+
// Express app: app.get('/path', handler)
|
|
355
|
+
/app\.(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
356
|
+
// Fastify: fastify.get('/path', handler)
|
|
357
|
+
/fastify\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
// ============================================
|
|
361
|
+
// PATTERN 4: NestJS/Decorators
|
|
362
|
+
// ============================================
|
|
363
|
+
const decoratorPatterns = [
|
|
364
|
+
/@(Get|Post|Put|Patch|Delete|All)\s*\(\s*['"`]?([^'"`\)]*)/gi,
|
|
365
|
+
/@Controller\s*\(\s*['"`]([^'"`]+)/gi,
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
// ============================================
|
|
369
|
+
// PATTERN 5: Frontend HTTP calls
|
|
370
|
+
// ============================================
|
|
371
|
+
const httpCallPatterns = [
|
|
372
|
+
// fetch() calls
|
|
373
|
+
/fetch\s*\(\s*[`'"](.*?)[`'"]/g,
|
|
374
|
+
/fetch\s*\(\s*`([^`]*)`/g,
|
|
375
|
+
// axios calls
|
|
376
|
+
/axios\.(get|post|put|patch|delete)\s*\(\s*[`'"](.*?)[`'"]/g,
|
|
377
|
+
/axios\s*\(\s*\{[^}]*url\s*:\s*[`'"](.*?)[`'"]/g,
|
|
378
|
+
// Angular HttpClient
|
|
379
|
+
/this\.http\.(get|post|put|patch|delete)\s*[<(]/g,
|
|
380
|
+
/httpClient\.(get|post|put|patch|delete)\s*[<(]/g,
|
|
381
|
+
// jQuery ajax
|
|
382
|
+
/\$\.(ajax|get|post)\s*\(\s*[`'"](.*?)[`'"]/g,
|
|
383
|
+
// Generic request libraries
|
|
384
|
+
/request\.(get|post|put|patch|delete)\s*\(/g,
|
|
385
|
+
/got\.(get|post|put|patch|delete)\s*\(/g,
|
|
386
|
+
/superagent\.(get|post|put|patch|delete)\s*\(/g,
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
// ============================================
|
|
390
|
+
// PATTERN 6: API URL definitions
|
|
391
|
+
// ============================================
|
|
392
|
+
const urlPatterns = [
|
|
393
|
+
// API endpoints in strings
|
|
394
|
+
/['"`](\/api\/[^'"`\s]+)['"`]/g,
|
|
395
|
+
/['"`](\/v\d+\/[^'"`\s]+)['"`]/g,
|
|
396
|
+
/['"`](https?:\/\/[^'"`\s]*\/api[^'"`\s]*)['"`]/g,
|
|
397
|
+
// Base URL definitions
|
|
398
|
+
/(?:API_BASE|API_URL|BASE_URL|baseURL|apiUrl|apiBase|API_ENDPOINT|BACKEND_URL)\s*[:=]\s*['"`]([^'"`]+)['"`]/gi,
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
// ============================================
|
|
402
|
+
// PATTERN 7: Method + URL combinations
|
|
403
|
+
// ============================================
|
|
404
|
+
const methodUrlPatterns = [
|
|
405
|
+
/(GET|POST|PUT|PATCH|DELETE)\s*[,:]?\s*['"`](\/[^'"`]+)['"`]/gi,
|
|
406
|
+
/method:\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/gi,
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
// Combine all patterns for line scanning
|
|
410
|
+
const allPatterns = [
|
|
411
|
+
...routerPatterns,
|
|
412
|
+
...decoratorPatterns,
|
|
413
|
+
...httpCallPatterns,
|
|
414
|
+
...urlPatterns,
|
|
415
|
+
...methodUrlPatterns
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
// If this is a route/controller file with swagger docs, include the whole file
|
|
419
|
+
if (isRouteFile && swaggerBlocks.length > 0) {
|
|
420
|
+
hasApiPatterns = true;
|
|
421
|
+
// Include entire file content (truncated if too long)
|
|
422
|
+
const maxLines = 300;
|
|
423
|
+
const truncatedContent = lines.length > maxLines
|
|
424
|
+
? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
|
|
425
|
+
: content;
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
hasApiPatterns: true,
|
|
429
|
+
content: `// File: ${filePath}\n// Route/Controller file with Swagger docs - full content:\n\n${truncatedContent}`
|
|
430
|
+
};
|
|
147
431
|
}
|
|
148
432
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
433
|
+
// If this is a route file without swagger, still include more content
|
|
434
|
+
if (isRouteFile) {
|
|
435
|
+
const hasRouteContent = allPatterns.some(p => {
|
|
436
|
+
p.lastIndex = 0;
|
|
437
|
+
return p.test(content);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
if (hasRouteContent) {
|
|
441
|
+
hasApiPatterns = true;
|
|
442
|
+
const maxLines = 200;
|
|
443
|
+
const truncatedContent = lines.length > maxLines
|
|
444
|
+
? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
|
|
445
|
+
: content;
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
hasApiPatterns: true,
|
|
449
|
+
content: `// File: ${filePath}\n// Route/Controller file - full content:\n\n${truncatedContent}`
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// If this is a DTO/types file, include full content
|
|
455
|
+
if (isDtoFile && typeBlocks.length > 0) {
|
|
456
|
+
hasApiPatterns = true;
|
|
457
|
+
const maxLines = 150;
|
|
458
|
+
const truncatedContent = lines.length > maxLines
|
|
459
|
+
? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
|
|
460
|
+
: content;
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
hasApiPatterns: true,
|
|
464
|
+
content: `// File: ${filePath}\n// DTO/Types file - full content:\n\n${truncatedContent}`
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// For non-route files, extract relevant sections
|
|
469
|
+
// First, extract imports and base URL definitions
|
|
470
|
+
const baseUrlLines = [];
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < Math.min(lines.length, 50); i++) {
|
|
473
|
+
const line = lines[i];
|
|
474
|
+
if (/(?:API_BASE|API_URL|BASE_URL|baseURL|apiUrl|BACKEND)/i.test(line)) {
|
|
475
|
+
baseUrlLines.push(`${i + 1}: ${line}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (baseUrlLines.length > 0) {
|
|
480
|
+
extractedBlocks.push('// Base URL definitions:\n' + baseUrlLines.join('\n'));
|
|
481
|
+
hasApiPatterns = true;
|
|
152
482
|
}
|
|
153
483
|
|
|
154
484
|
// Line-by-line extraction with context
|
|
155
485
|
for (let i = 0; i < lines.length; i++) {
|
|
156
486
|
const line = lines[i];
|
|
157
487
|
|
|
158
|
-
// Check
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
488
|
+
// Check all patterns
|
|
489
|
+
let hasPattern = false;
|
|
490
|
+
for (const pattern of allPatterns) {
|
|
491
|
+
pattern.lastIndex = 0;
|
|
492
|
+
if (pattern.test(line)) {
|
|
493
|
+
hasPattern = true;
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
162
497
|
|
|
163
498
|
// Also check for common API keywords
|
|
164
|
-
const hasKeyword =
|
|
165
|
-
|
|
166
|
-
// Check if this is a POST/PUT request - need more context for body structure
|
|
167
|
-
const isPostRequest = /method:\s*['"]POST|\.post\(|method:\s*['"]PUT|\.put\(/i.test(line);
|
|
499
|
+
const hasKeyword = /\.get\(|\.post\(|\.put\(|\.patch\(|\.delete\(|fetch\(|axios|\/api\/|endpoint|@Get|@Post|@Put|@Delete/i.test(line);
|
|
168
500
|
|
|
169
501
|
if (hasPattern || hasKeyword) {
|
|
170
|
-
|
|
502
|
+
hasApiPatterns = true;
|
|
171
503
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
const contextBefore =
|
|
175
|
-
const contextAfter =
|
|
504
|
+
// Determine context needed
|
|
505
|
+
const isPostOrPut = /post|put|patch/i.test(line);
|
|
506
|
+
const contextBefore = isPostOrPut ? 20 : 5; // More context for mutations
|
|
507
|
+
const contextAfter = isPostOrPut ? 10 : 5;
|
|
176
508
|
|
|
177
509
|
const startLine = Math.max(0, i - contextBefore);
|
|
178
510
|
const endLine = Math.min(lines.length - 1, i + contextAfter);
|
|
@@ -188,49 +520,16 @@ function extractApiPatterns(content, filePath) {
|
|
|
188
520
|
}
|
|
189
521
|
}
|
|
190
522
|
|
|
191
|
-
|
|
192
|
-
|
|
523
|
+
if (hasApiPatterns && extractedBlocks.length > 0) {
|
|
524
|
+
// Deduplicate blocks
|
|
525
|
+
const uniqueBlocks = [...new Set(extractedBlocks)];
|
|
193
526
|
return {
|
|
194
|
-
|
|
195
|
-
content: `// File: ${filePath}\n// API patterns
|
|
527
|
+
hasApiPatterns: true,
|
|
528
|
+
content: `// File: ${filePath}\n// API patterns extracted:\n\n${uniqueBlocks.join('\n\n// ---\n\n')}`
|
|
196
529
|
};
|
|
197
530
|
}
|
|
198
531
|
|
|
199
|
-
|
|
200
|
-
if (/api|fetch|axios|service|http/i.test(filePath)) {
|
|
201
|
-
return {
|
|
202
|
-
hasApiCalls: true,
|
|
203
|
-
content: `// File: ${filePath}\n${content.slice(0, 5000)}`
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return { hasApiCalls: false, content: '' };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Prioritize frontend files based on keywords
|
|
212
|
-
*/
|
|
213
|
-
function prioritizeFiles(files, basePath) {
|
|
214
|
-
return files.sort((a, b) => {
|
|
215
|
-
const aPath = path.relative(basePath, a).toLowerCase();
|
|
216
|
-
const bPath = path.relative(basePath, b).toLowerCase();
|
|
217
|
-
|
|
218
|
-
// Deprioritize test files
|
|
219
|
-
if (aPath.includes('test') || aPath.includes('spec')) return 1;
|
|
220
|
-
if (bPath.includes('test') || bPath.includes('spec')) return -1;
|
|
221
|
-
|
|
222
|
-
// Prioritize src/frontend folders
|
|
223
|
-
if (aPath.includes('src/') || aPath.includes('frontend/')) {
|
|
224
|
-
if (!bPath.includes('src/') && !bPath.includes('frontend/')) return -1;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const aScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
|
|
228
|
-
aPath.includes(kw) ? score + 1 : score, 0);
|
|
229
|
-
const bScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
|
|
230
|
-
bPath.includes(kw) ? score + 1 : score, 0);
|
|
231
|
-
|
|
232
|
-
return bScore - aScore;
|
|
233
|
-
});
|
|
532
|
+
return { hasApiPatterns: false, content: '' };
|
|
234
533
|
}
|
|
235
534
|
|
|
236
535
|
module.exports = { scanCodebase };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merlean/analyzer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "AI Bot codebase analyzer - generates site maps for AI assistant integration",
|
|
5
5
|
"keywords": ["ai", "bot", "analyzer", "claude", "anthropic", "widget"],
|
|
6
6
|
"author": "zmaren",
|