@merlean/analyzer 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/analyzer.js +134 -26
- package/package.json +1 -1
package/lib/analyzer.js
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codebase Scanner
|
|
2
|
+
* Frontend-focused Codebase Scanner
|
|
3
3
|
*
|
|
4
|
-
* Scans
|
|
5
|
-
*
|
|
4
|
+
* Scans FRONTEND code to learn how it communicates with the backend.
|
|
5
|
+
* Extracts: fetch(), axios, $.ajax, API calls, form submissions
|
|
6
|
+
*
|
|
7
|
+
* This is what the bot needs - it runs in the browser and should know
|
|
8
|
+
* what API calls the frontend already makes.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
const fs = require('fs');
|
|
9
12
|
const path = require('path');
|
|
10
13
|
const { glob } = require('glob');
|
|
11
14
|
|
|
12
|
-
//
|
|
13
|
-
const
|
|
15
|
+
// Frontend file patterns (prioritize frontend code)
|
|
16
|
+
const FRONTEND_PATTERNS = [
|
|
14
17
|
'**/*.js',
|
|
15
|
-
'**/*.ts',
|
|
16
18
|
'**/*.jsx',
|
|
19
|
+
'**/*.ts',
|
|
17
20
|
'**/*.tsx',
|
|
18
|
-
'**/*.
|
|
19
|
-
'**/*.
|
|
20
|
-
'
|
|
21
|
+
'**/*.vue',
|
|
22
|
+
'**/*.svelte',
|
|
23
|
+
'**/app.js',
|
|
24
|
+
'**/main.js',
|
|
25
|
+
'**/index.js',
|
|
26
|
+
'**/*api*.js',
|
|
27
|
+
'**/*service*.js',
|
|
28
|
+
'**/*fetch*.js',
|
|
29
|
+
'**/*http*.js'
|
|
21
30
|
];
|
|
22
31
|
|
|
23
32
|
// Directories to ignore
|
|
@@ -30,23 +39,28 @@ const IGNORE_PATTERNS = [
|
|
|
30
39
|
'**/__pycache__/**',
|
|
31
40
|
'**/venv/**',
|
|
32
41
|
'**/*.min.js',
|
|
33
|
-
'**/*.map'
|
|
42
|
+
'**/*.map',
|
|
43
|
+
'**/server.js', // Skip backend files
|
|
44
|
+
'**/server/**',
|
|
45
|
+
'**/backend/**',
|
|
46
|
+
'**/api/**', // Skip backend API folders
|
|
47
|
+
'**/controllers/**'
|
|
34
48
|
];
|
|
35
49
|
|
|
36
50
|
// Keywords to prioritize files
|
|
37
51
|
const PRIORITY_KEYWORDS = [
|
|
38
|
-
'
|
|
39
|
-
'
|
|
52
|
+
'fetch', 'axios', 'api', 'service', 'http', 'request',
|
|
53
|
+
'ajax', 'client', 'frontend', 'app', 'main', 'store'
|
|
40
54
|
];
|
|
41
55
|
|
|
42
56
|
/**
|
|
43
|
-
* Scan codebase and collect
|
|
57
|
+
* Scan codebase and collect frontend API patterns
|
|
44
58
|
*/
|
|
45
59
|
async function scanCodebase(codebasePath) {
|
|
46
|
-
console.log(' Scanning files...');
|
|
60
|
+
console.log(' Scanning frontend files...');
|
|
47
61
|
|
|
48
62
|
// Get files to scan
|
|
49
|
-
const files = await glob(
|
|
63
|
+
const files = await glob(FRONTEND_PATTERNS, {
|
|
50
64
|
cwd: codebasePath,
|
|
51
65
|
ignore: IGNORE_PATTERNS,
|
|
52
66
|
absolute: true
|
|
@@ -54,42 +68,136 @@ async function scanCodebase(codebasePath) {
|
|
|
54
68
|
|
|
55
69
|
console.log(` Found ${files.length} files`);
|
|
56
70
|
|
|
57
|
-
// Prioritize
|
|
71
|
+
// Prioritize frontend-focused files
|
|
58
72
|
const prioritizedFiles = prioritizeFiles(files, codebasePath);
|
|
59
|
-
const filesToAnalyze = prioritizedFiles.slice(0,
|
|
73
|
+
const filesToAnalyze = prioritizedFiles.slice(0, 30); // Fewer files, but more content
|
|
60
74
|
|
|
61
|
-
console.log(`
|
|
75
|
+
console.log(` Analyzing ${filesToAnalyze.length} frontend files...`);
|
|
62
76
|
|
|
63
|
-
// Read and
|
|
77
|
+
// Read and extract API patterns from files
|
|
64
78
|
const fileContents = [];
|
|
65
79
|
for (const file of filesToAnalyze) {
|
|
66
80
|
try {
|
|
67
81
|
const content = fs.readFileSync(file, 'utf-8');
|
|
68
82
|
const relativePath = path.relative(codebasePath, file);
|
|
69
83
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
84
|
+
// Extract API calls from the file
|
|
85
|
+
const extracted = extractApiPatterns(content, relativePath);
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
if (extracted.hasApiCalls) {
|
|
88
|
+
fileContents.push({
|
|
89
|
+
path: relativePath,
|
|
90
|
+
content: extracted.content
|
|
91
|
+
});
|
|
92
|
+
}
|
|
77
93
|
} catch (error) {
|
|
78
94
|
// Skip files that can't be read
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
|
|
98
|
+
console.log(` Found API patterns in ${fileContents.length} files`);
|
|
99
|
+
|
|
82
100
|
return fileContents;
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
/**
|
|
86
|
-
*
|
|
104
|
+
* Extract API call patterns from file content
|
|
105
|
+
*/
|
|
106
|
+
function extractApiPatterns(content, filePath) {
|
|
107
|
+
const apiPatterns = [];
|
|
108
|
+
const lines = content.split('\n');
|
|
109
|
+
|
|
110
|
+
// Patterns that indicate API calls
|
|
111
|
+
const patterns = [
|
|
112
|
+
// fetch() calls
|
|
113
|
+
{ regex: /fetch\s*\(\s*[`'"](.*?)[`'"]/g, type: 'fetch' },
|
|
114
|
+
{ regex: /fetch\s*\(\s*`([^`]*)`/g, type: 'fetch-template' },
|
|
115
|
+
{ regex: /fetch\s*\(\s*(['"])?\/api\//g, type: 'fetch-api' },
|
|
116
|
+
|
|
117
|
+
// axios calls
|
|
118
|
+
{ regex: /axios\.(get|post|put|patch|delete)\s*\(\s*[`'"](.*?)[`'"]/g, type: 'axios' },
|
|
119
|
+
{ regex: /axios\s*\(\s*\{[^}]*url\s*:\s*[`'"](.*?)[`'"]/g, type: 'axios-config' },
|
|
120
|
+
|
|
121
|
+
// jQuery ajax
|
|
122
|
+
{ regex: /\$\.(ajax|get|post)\s*\(\s*[`'"](.*?)[`'"]/g, type: 'jquery' },
|
|
123
|
+
|
|
124
|
+
// Generic API URLs
|
|
125
|
+
{ regex: /['"`](\/api\/[^'"`\s]+)['"`]/g, type: 'api-url' },
|
|
126
|
+
{ regex: /['"`](https?:\/\/[^'"`\s]*\/api[^'"`\s]*)['"`]/g, type: 'full-url' },
|
|
127
|
+
|
|
128
|
+
// Method + URL patterns
|
|
129
|
+
{ regex: /(GET|POST|PUT|PATCH|DELETE)\s*[,:]?\s*['"`](\/[^'"`]+)['"`]/gi, type: 'method-url' },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
let hasApiCalls = false;
|
|
133
|
+
const extractedBlocks = [];
|
|
134
|
+
|
|
135
|
+
// Line-by-line extraction with context
|
|
136
|
+
for (let i = 0; i < lines.length; i++) {
|
|
137
|
+
const line = lines[i];
|
|
138
|
+
|
|
139
|
+
// Check for API patterns
|
|
140
|
+
const hasPattern = patterns.some(p => p.regex.test(line));
|
|
141
|
+
// Reset regex lastIndex
|
|
142
|
+
patterns.forEach(p => p.regex.lastIndex = 0);
|
|
143
|
+
|
|
144
|
+
// Also check for common API keywords
|
|
145
|
+
const hasKeyword = /fetch|axios|\.ajax|\.get\(|\.post\(|\.put\(|\.delete\(|\/api\/|endpoint|baseURL/i.test(line);
|
|
146
|
+
|
|
147
|
+
if (hasPattern || hasKeyword) {
|
|
148
|
+
hasApiCalls = true;
|
|
149
|
+
|
|
150
|
+
// Get context: 3 lines before and 5 lines after
|
|
151
|
+
const startLine = Math.max(0, i - 3);
|
|
152
|
+
const endLine = Math.min(lines.length - 1, i + 5);
|
|
153
|
+
|
|
154
|
+
const block = lines.slice(startLine, endLine + 1)
|
|
155
|
+
.map((l, idx) => `${startLine + idx + 1}: ${l}`)
|
|
156
|
+
.join('\n');
|
|
157
|
+
|
|
158
|
+
extractedBlocks.push(block);
|
|
159
|
+
|
|
160
|
+
// Skip ahead to avoid duplicates
|
|
161
|
+
i = endLine;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If we found API patterns, return extracted content
|
|
166
|
+
if (hasApiCalls && extractedBlocks.length > 0) {
|
|
167
|
+
return {
|
|
168
|
+
hasApiCalls: true,
|
|
169
|
+
content: `// File: ${filePath}\n// API patterns found:\n\n${extractedBlocks.join('\n\n// ---\n\n')}`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fallback: include first 5000 chars if file looks relevant
|
|
174
|
+
if (/api|fetch|axios|service|http/i.test(filePath)) {
|
|
175
|
+
return {
|
|
176
|
+
hasApiCalls: true,
|
|
177
|
+
content: `// File: ${filePath}\n${content.slice(0, 5000)}`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { hasApiCalls: false, content: '' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Prioritize frontend files based on keywords
|
|
87
186
|
*/
|
|
88
187
|
function prioritizeFiles(files, basePath) {
|
|
89
188
|
return files.sort((a, b) => {
|
|
90
189
|
const aPath = path.relative(basePath, a).toLowerCase();
|
|
91
190
|
const bPath = path.relative(basePath, b).toLowerCase();
|
|
92
191
|
|
|
192
|
+
// Deprioritize test files
|
|
193
|
+
if (aPath.includes('test') || aPath.includes('spec')) return 1;
|
|
194
|
+
if (bPath.includes('test') || bPath.includes('spec')) return -1;
|
|
195
|
+
|
|
196
|
+
// Prioritize src/frontend folders
|
|
197
|
+
if (aPath.includes('src/') || aPath.includes('frontend/')) {
|
|
198
|
+
if (!bPath.includes('src/') && !bPath.includes('frontend/')) return -1;
|
|
199
|
+
}
|
|
200
|
+
|
|
93
201
|
const aScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
|
|
94
202
|
aPath.includes(kw) ? score + 1 : score, 0);
|
|
95
203
|
const bScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merlean/analyzer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|