@merlean/analyzer 1.2.0 → 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 +118 -84
- 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,116 +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
|
-
|
|
72
|
-
if (content.length > 8000) {
|
|
73
|
-
// For large files, extract route definitions and API patterns
|
|
74
|
-
extractedContent = extractRoutePatterns(content, relativePath);
|
|
75
|
-
} else {
|
|
76
|
-
// For smaller files, include more content
|
|
77
|
-
extractedContent = content.slice(0, 8000);
|
|
78
|
-
}
|
|
84
|
+
// Extract API calls from the file
|
|
85
|
+
const extracted = extractApiPatterns(content, relativePath);
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
if (extracted.hasApiCalls) {
|
|
88
|
+
fileContents.push({
|
|
89
|
+
path: relativePath,
|
|
90
|
+
content: extracted.content
|
|
91
|
+
});
|
|
92
|
+
}
|
|
84
93
|
} catch (error) {
|
|
85
94
|
// Skip files that can't be read
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
console.log(` Found API patterns in ${fileContents.length} files`);
|
|
99
|
+
|
|
89
100
|
return fileContents;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
/**
|
|
93
|
-
* Extract
|
|
104
|
+
* Extract API call patterns from file content
|
|
94
105
|
*/
|
|
95
|
-
function
|
|
106
|
+
function extractApiPatterns(content, filePath) {
|
|
107
|
+
const apiPatterns = [];
|
|
96
108
|
const lines = content.split('\n');
|
|
97
|
-
const relevantLines = [];
|
|
98
109
|
|
|
99
|
-
// Patterns that indicate API
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
/
|
|
103
|
-
/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/
|
|
108
|
-
/axios
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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' },
|
|
112
130
|
];
|
|
113
|
-
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
|
|
132
|
+
let hasApiCalls = false;
|
|
133
|
+
const extractedBlocks = [];
|
|
134
|
+
|
|
135
|
+
// Line-by-line extraction with context
|
|
117
136
|
for (let i = 0; i < lines.length; i++) {
|
|
118
137
|
const line = lines[i];
|
|
119
138
|
|
|
120
|
-
// Check
|
|
121
|
-
const
|
|
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);
|
|
122
143
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
relevantLines.push(`// Line ${i + 1}: ${line}`);
|
|
131
|
-
inRouteBlock = true;
|
|
132
|
-
braceCount = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
133
|
-
} else if (inRouteBlock) {
|
|
134
|
-
// Continue capturing the route handler
|
|
135
|
-
relevantLines.push(line);
|
|
136
|
-
braceCount += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
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;
|
|
137
149
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
relevantLines.push('// ---');
|
|
142
|
-
}
|
|
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);
|
|
143
153
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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;
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
|
-
|
|
151
|
-
// If we found
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
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
|
+
};
|
|
154
171
|
}
|
|
155
|
-
|
|
156
|
-
|
|
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: '' };
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
/**
|
|
160
|
-
* Prioritize files based on keywords
|
|
185
|
+
* Prioritize frontend files based on keywords
|
|
161
186
|
*/
|
|
162
187
|
function prioritizeFiles(files, basePath) {
|
|
163
188
|
return files.sort((a, b) => {
|
|
164
189
|
const aPath = path.relative(basePath, a).toLowerCase();
|
|
165
190
|
const bPath = path.relative(basePath, b).toLowerCase();
|
|
166
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
|
+
|
|
167
201
|
const aScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
|
|
168
202
|
aPath.includes(kw) ? score + 1 : score, 0);
|
|
169
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",
|