nexusforge-cli 1.1.1 → 1.2.1
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/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +183 -17
- package/dist/components/App.js.map +1 -1
- package/dist/index.js +462 -10
- package/dist/index.js.map +1 -1
- package/dist/modules/commandEngine.d.ts +70 -0
- package/dist/modules/commandEngine.d.ts.map +1 -0
- package/dist/modules/commandEngine.js +672 -0
- package/dist/modules/commandEngine.js.map +1 -0
- package/dist/modules/contextBuilder.d.ts +51 -0
- package/dist/modules/contextBuilder.d.ts.map +1 -0
- package/dist/modules/contextBuilder.js +725 -0
- package/dist/modules/contextBuilder.js.map +1 -0
- package/dist/modules/domainDetector.d.ts +64 -0
- package/dist/modules/domainDetector.d.ts.map +1 -0
- package/dist/modules/domainDetector.js +722 -0
- package/dist/modules/domainDetector.js.map +1 -0
- package/dist/modules/fileOperations.d.ts +99 -0
- package/dist/modules/fileOperations.d.ts.map +1 -0
- package/dist/modules/fileOperations.js +543 -0
- package/dist/modules/fileOperations.js.map +1 -0
- package/dist/modules/forgeEngine.d.ts +153 -0
- package/dist/modules/forgeEngine.d.ts.map +1 -0
- package/dist/modules/forgeEngine.js +652 -0
- package/dist/modules/forgeEngine.js.map +1 -0
- package/dist/modules/gitManager.d.ts +151 -0
- package/dist/modules/gitManager.d.ts.map +1 -0
- package/dist/modules/gitManager.js +539 -0
- package/dist/modules/gitManager.js.map +1 -0
- package/dist/modules/index.d.ts +25 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +25 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/prdProcessor.d.ts +125 -0
- package/dist/modules/prdProcessor.d.ts.map +1 -0
- package/dist/modules/prdProcessor.js +466 -0
- package/dist/modules/prdProcessor.js.map +1 -0
- package/dist/modules/projectScanner.d.ts +105 -0
- package/dist/modules/projectScanner.d.ts.map +1 -0
- package/dist/modules/projectScanner.js +859 -0
- package/dist/modules/projectScanner.js.map +1 -0
- package/dist/modules/safetyGuard.d.ts +83 -0
- package/dist/modules/safetyGuard.d.ts.map +1 -0
- package/dist/modules/safetyGuard.js +492 -0
- package/dist/modules/safetyGuard.js.map +1 -0
- package/dist/modules/templateManager.d.ts +78 -0
- package/dist/modules/templateManager.d.ts.map +1 -0
- package/dist/modules/templateManager.js +556 -0
- package/dist/modules/templateManager.js.map +1 -0
- package/dist/native/index.d.ts +125 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +164 -0
- package/dist/native/index.js.map +1 -0
- package/dist/services/executor.d.ts +24 -0
- package/dist/services/executor.d.ts.map +1 -1
- package/dist/services/executor.js +149 -6
- package/dist/services/executor.js.map +1 -1
- package/dist/services/fileManager.d.ts +134 -0
- package/dist/services/fileManager.d.ts.map +1 -0
- package/dist/services/fileManager.js +489 -0
- package/dist/services/fileManager.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Scanner for NexusForge CLI
|
|
3
|
+
* Analyzes project structure, dependencies, and content
|
|
4
|
+
* Ported from Python nexusforge/modules/project/scanner.py
|
|
5
|
+
*
|
|
6
|
+
* Now with optional native Rust acceleration (10-20x faster)
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as native from '../native/index.js';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// PROJECT SCANNER CLASS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export class ProjectScanner {
|
|
15
|
+
projectPath;
|
|
16
|
+
useCache;
|
|
17
|
+
cacheDir;
|
|
18
|
+
cacheFile;
|
|
19
|
+
files = new Map();
|
|
20
|
+
dependencies = {};
|
|
21
|
+
technologies = new Set();
|
|
22
|
+
stats;
|
|
23
|
+
// File extensions to language mapping
|
|
24
|
+
static LANGUAGE_MAP = {
|
|
25
|
+
// Python
|
|
26
|
+
'.py': 'python',
|
|
27
|
+
'.pyx': 'python',
|
|
28
|
+
'.pyi': 'python',
|
|
29
|
+
// JavaScript/TypeScript
|
|
30
|
+
'.js': 'javascript',
|
|
31
|
+
'.jsx': 'javascript',
|
|
32
|
+
'.ts': 'typescript',
|
|
33
|
+
'.tsx': 'typescript',
|
|
34
|
+
'.mjs': 'javascript',
|
|
35
|
+
'.cjs': 'javascript',
|
|
36
|
+
// Web
|
|
37
|
+
'.html': 'html',
|
|
38
|
+
'.htm': 'html',
|
|
39
|
+
'.css': 'css',
|
|
40
|
+
'.scss': 'scss',
|
|
41
|
+
'.sass': 'sass',
|
|
42
|
+
'.less': 'less',
|
|
43
|
+
'.vue': 'vue',
|
|
44
|
+
'.svelte': 'svelte',
|
|
45
|
+
// Systems
|
|
46
|
+
'.rs': 'rust',
|
|
47
|
+
'.go': 'go',
|
|
48
|
+
'.c': 'c',
|
|
49
|
+
'.cpp': 'cpp',
|
|
50
|
+
'.cc': 'cpp',
|
|
51
|
+
'.cxx': 'cpp',
|
|
52
|
+
'.h': 'c',
|
|
53
|
+
'.hpp': 'cpp',
|
|
54
|
+
'.hxx': 'cpp',
|
|
55
|
+
// JVM
|
|
56
|
+
'.java': 'java',
|
|
57
|
+
'.kt': 'kotlin',
|
|
58
|
+
'.kts': 'kotlin',
|
|
59
|
+
'.scala': 'scala',
|
|
60
|
+
'.groovy': 'groovy',
|
|
61
|
+
// .NET
|
|
62
|
+
'.cs': 'csharp',
|
|
63
|
+
'.fs': 'fsharp',
|
|
64
|
+
'.vb': 'vb',
|
|
65
|
+
// Other
|
|
66
|
+
'.rb': 'ruby',
|
|
67
|
+
'.php': 'php',
|
|
68
|
+
'.swift': 'swift',
|
|
69
|
+
'.r': 'r',
|
|
70
|
+
'.m': 'objective-c',
|
|
71
|
+
'.jl': 'julia',
|
|
72
|
+
'.sh': 'bash',
|
|
73
|
+
'.bash': 'bash',
|
|
74
|
+
'.zsh': 'zsh',
|
|
75
|
+
'.fish': 'fish',
|
|
76
|
+
'.ps1': 'powershell',
|
|
77
|
+
'.sql': 'sql',
|
|
78
|
+
'.lua': 'lua',
|
|
79
|
+
'.pl': 'perl',
|
|
80
|
+
'.ex': 'elixir',
|
|
81
|
+
'.exs': 'elixir',
|
|
82
|
+
'.erl': 'erlang',
|
|
83
|
+
'.hrl': 'erlang',
|
|
84
|
+
'.dart': 'dart',
|
|
85
|
+
};
|
|
86
|
+
// Directories to always ignore
|
|
87
|
+
static IGNORE_DIRS = new Set([
|
|
88
|
+
'.git', '.svn', '.hg', '.bzr',
|
|
89
|
+
'__pycache__', '.pytest_cache', '.mypy_cache',
|
|
90
|
+
'node_modules', 'bower_components',
|
|
91
|
+
'.nexusforge', '.vscode', '.idea',
|
|
92
|
+
'venv', 'env', '.env', 'virtualenv',
|
|
93
|
+
'dist', 'build', 'target', 'out',
|
|
94
|
+
'.next', '.nuxt', '.cache',
|
|
95
|
+
'coverage', '.nyc_output',
|
|
96
|
+
'vendor', 'packages',
|
|
97
|
+
]);
|
|
98
|
+
constructor(projectPath, useCache = true) {
|
|
99
|
+
this.projectPath = path.resolve(projectPath);
|
|
100
|
+
this.useCache = useCache;
|
|
101
|
+
this.cacheDir = path.join(this.projectPath, '.nexusforge');
|
|
102
|
+
this.cacheFile = path.join(this.cacheDir, 'project_scan.cache.json');
|
|
103
|
+
// Ensure cache directory exists
|
|
104
|
+
if (!fs.existsSync(this.cacheDir)) {
|
|
105
|
+
try {
|
|
106
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Ignore errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.stats = {
|
|
113
|
+
filesScanned: 0,
|
|
114
|
+
filesSkipped: 0,
|
|
115
|
+
totalSize: 0,
|
|
116
|
+
scanErrors: 0,
|
|
117
|
+
cacheEnabled: useCache,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// ========================================================================
|
|
121
|
+
// CACHING
|
|
122
|
+
// ========================================================================
|
|
123
|
+
loadCache() {
|
|
124
|
+
if (!this.useCache || !fs.existsSync(this.cacheFile)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const data = JSON.parse(fs.readFileSync(this.cacheFile, 'utf-8'));
|
|
129
|
+
// Check if cache is less than 1 hour old
|
|
130
|
+
const cacheAge = Date.now() - (data.scanTime || 0);
|
|
131
|
+
if (cacheAge < 3600000) {
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Ignore errors
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
saveCache(result) {
|
|
141
|
+
if (!this.useCache)
|
|
142
|
+
return;
|
|
143
|
+
try {
|
|
144
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(result, null, 2));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ========================================================================
|
|
151
|
+
// SCANNING
|
|
152
|
+
// ========================================================================
|
|
153
|
+
/**
|
|
154
|
+
* Scan using native Rust module (10-20x faster)
|
|
155
|
+
* Returns null if native module not available
|
|
156
|
+
*/
|
|
157
|
+
scanNative() {
|
|
158
|
+
const nativeResult = native.scanDirectory({
|
|
159
|
+
root: this.projectPath,
|
|
160
|
+
extractImports: true,
|
|
161
|
+
extractSymbols: true,
|
|
162
|
+
computeHashes: false,
|
|
163
|
+
});
|
|
164
|
+
if (!nativeResult) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Convert native result to our format
|
|
168
|
+
for (const file of nativeResult.files) {
|
|
169
|
+
const relPath = file.relativePath;
|
|
170
|
+
const lang = file.language?.toLowerCase() || 'unknown';
|
|
171
|
+
this.files.set(relPath, {
|
|
172
|
+
path: file.path,
|
|
173
|
+
language: lang,
|
|
174
|
+
size: file.size,
|
|
175
|
+
lines: file.lineCount,
|
|
176
|
+
imports: file.imports,
|
|
177
|
+
functions: file.functions,
|
|
178
|
+
classes: file.classes,
|
|
179
|
+
complexity: 0,
|
|
180
|
+
lastModified: null,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
this.stats.filesScanned = nativeResult.stats.totalFiles;
|
|
184
|
+
this.stats.totalSize = nativeResult.stats.totalSize;
|
|
185
|
+
// Build language stats
|
|
186
|
+
const languages = {};
|
|
187
|
+
for (const [lang, count] of Object.entries(nativeResult.stats.filesByLanguage)) {
|
|
188
|
+
languages[lang.toLowerCase()] = count;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
projectPath: this.projectPath,
|
|
192
|
+
fileCount: nativeResult.stats.totalFiles,
|
|
193
|
+
totalLines: nativeResult.stats.totalLines,
|
|
194
|
+
totalSize: nativeResult.stats.totalSize,
|
|
195
|
+
languages,
|
|
196
|
+
technologies: [], // Will be filled by detectTechnologies
|
|
197
|
+
dependencies: {},
|
|
198
|
+
structure: {},
|
|
199
|
+
entryPoints: [],
|
|
200
|
+
configFiles: [],
|
|
201
|
+
testFiles: [],
|
|
202
|
+
scanTime: Date.now(),
|
|
203
|
+
scanDuration: nativeResult.stats.scanDurationMs / 1000,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
scan() {
|
|
207
|
+
const startTime = Date.now();
|
|
208
|
+
// Check cache
|
|
209
|
+
const cached = this.loadCache();
|
|
210
|
+
if (cached) {
|
|
211
|
+
return cached;
|
|
212
|
+
}
|
|
213
|
+
// Try native scan first (10-20x faster)
|
|
214
|
+
let nativeResult = null;
|
|
215
|
+
if (native.isNativeAvailable()) {
|
|
216
|
+
nativeResult = this.scanNative();
|
|
217
|
+
}
|
|
218
|
+
if (nativeResult) {
|
|
219
|
+
// Native scan succeeded, complete with additional analysis
|
|
220
|
+
this.detectTechnologies();
|
|
221
|
+
this.analyzeDependencies();
|
|
222
|
+
const result = {
|
|
223
|
+
...nativeResult,
|
|
224
|
+
technologies: Array.from(this.technologies),
|
|
225
|
+
dependencies: this.dependencies,
|
|
226
|
+
structure: this.getDirectoryStructure(),
|
|
227
|
+
entryPoints: this.findEntryPoints(),
|
|
228
|
+
configFiles: this.findConfigFiles(),
|
|
229
|
+
testFiles: this.findTestFiles(),
|
|
230
|
+
scanDuration: (Date.now() - startTime) / 1000,
|
|
231
|
+
};
|
|
232
|
+
this.saveCache(result);
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
// Fallback to TypeScript scan
|
|
236
|
+
this.scanFiles();
|
|
237
|
+
this.detectTechnologies();
|
|
238
|
+
this.analyzeDependencies();
|
|
239
|
+
// Create result
|
|
240
|
+
const result = {
|
|
241
|
+
projectPath: this.projectPath,
|
|
242
|
+
fileCount: this.files.size,
|
|
243
|
+
totalLines: Array.from(this.files.values()).reduce((sum, f) => sum + f.lines, 0),
|
|
244
|
+
totalSize: this.stats.totalSize,
|
|
245
|
+
languages: this.getLanguageStats(),
|
|
246
|
+
technologies: Array.from(this.technologies),
|
|
247
|
+
dependencies: this.dependencies,
|
|
248
|
+
structure: this.getDirectoryStructure(),
|
|
249
|
+
entryPoints: this.findEntryPoints(),
|
|
250
|
+
configFiles: this.findConfigFiles(),
|
|
251
|
+
testFiles: this.findTestFiles(),
|
|
252
|
+
scanTime: Date.now(),
|
|
253
|
+
scanDuration: (Date.now() - startTime) / 1000,
|
|
254
|
+
};
|
|
255
|
+
// Save cache
|
|
256
|
+
this.saveCache(result);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
quickScan() {
|
|
260
|
+
let fileCount = 0;
|
|
261
|
+
const techHints = new Set();
|
|
262
|
+
const languages = new Set();
|
|
263
|
+
const scan = (dir) => {
|
|
264
|
+
try {
|
|
265
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
266
|
+
for (const entry of entries) {
|
|
267
|
+
const fullPath = path.join(dir, entry.name);
|
|
268
|
+
if (entry.isDirectory()) {
|
|
269
|
+
if (!this.shouldIgnore(fullPath)) {
|
|
270
|
+
scan(fullPath);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
275
|
+
if (ProjectScanner.LANGUAGE_MAP[ext]) {
|
|
276
|
+
fileCount++;
|
|
277
|
+
languages.add(ProjectScanner.LANGUAGE_MAP[ext]);
|
|
278
|
+
}
|
|
279
|
+
this.quickDetectTech(entry.name, techHints);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Ignore errors
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
scan(this.projectPath);
|
|
288
|
+
return {
|
|
289
|
+
projectPath: this.projectPath,
|
|
290
|
+
fileCount,
|
|
291
|
+
languages: Array.from(languages),
|
|
292
|
+
technologies: Array.from(techHints),
|
|
293
|
+
quickScan: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
quickDetectTech(filename, techHints) {
|
|
297
|
+
const techFiles = {
|
|
298
|
+
'package.json': 'nodejs',
|
|
299
|
+
'yarn.lock': 'yarn',
|
|
300
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
301
|
+
'Cargo.toml': 'rust',
|
|
302
|
+
'requirements.txt': 'python',
|
|
303
|
+
'Pipfile': 'pipenv',
|
|
304
|
+
'pyproject.toml': 'python',
|
|
305
|
+
'go.mod': 'go',
|
|
306
|
+
'pom.xml': 'maven',
|
|
307
|
+
'build.gradle': 'gradle',
|
|
308
|
+
'Dockerfile': 'docker',
|
|
309
|
+
};
|
|
310
|
+
if (techFiles[filename]) {
|
|
311
|
+
techHints.add(techFiles[filename]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
shouldIgnore(filepath) {
|
|
315
|
+
const parts = filepath.split(path.sep);
|
|
316
|
+
for (const part of parts) {
|
|
317
|
+
if (ProjectScanner.IGNORE_DIRS.has(part)) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const basename = path.basename(filepath);
|
|
322
|
+
if (basename.startsWith('.') && !['.gitignore', '.env', '.env.example', '.dockerignore'].includes(basename)) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
scanFiles() {
|
|
328
|
+
const scan = (dir) => {
|
|
329
|
+
try {
|
|
330
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
const fullPath = path.join(dir, entry.name);
|
|
333
|
+
if (entry.isDirectory()) {
|
|
334
|
+
if (!this.shouldIgnore(fullPath)) {
|
|
335
|
+
scan(fullPath);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
340
|
+
if (ProjectScanner.LANGUAGE_MAP[ext]) {
|
|
341
|
+
this.scanFile(fullPath);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Ignore errors
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
scan(this.projectPath);
|
|
351
|
+
}
|
|
352
|
+
scanFile(filePath) {
|
|
353
|
+
try {
|
|
354
|
+
const stat = fs.statSync(filePath);
|
|
355
|
+
// Skip very large files (>10MB)
|
|
356
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
357
|
+
this.stats.filesSkipped++;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const relPath = path.relative(this.projectPath, filePath);
|
|
361
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
362
|
+
const language = ProjectScanner.LANGUAGE_MAP[ext] || 'unknown';
|
|
363
|
+
const fileInfo = {
|
|
364
|
+
path: filePath,
|
|
365
|
+
language,
|
|
366
|
+
size: stat.size,
|
|
367
|
+
lines: this.countLines(filePath),
|
|
368
|
+
imports: this.extractImports(filePath, language),
|
|
369
|
+
functions: this.extractFunctions(filePath, language),
|
|
370
|
+
classes: this.extractClasses(filePath, language),
|
|
371
|
+
complexity: 0,
|
|
372
|
+
lastModified: stat.mtimeMs,
|
|
373
|
+
};
|
|
374
|
+
this.files.set(relPath, fileInfo);
|
|
375
|
+
this.stats.filesScanned++;
|
|
376
|
+
this.stats.totalSize += stat.size;
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
this.stats.filesSkipped++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ========================================================================
|
|
383
|
+
// FILE ANALYSIS
|
|
384
|
+
// ========================================================================
|
|
385
|
+
countLines(filePath) {
|
|
386
|
+
try {
|
|
387
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
388
|
+
return content.split('\n').length;
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
extractImports(filePath, language) {
|
|
395
|
+
const imports = [];
|
|
396
|
+
try {
|
|
397
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
398
|
+
if (language === 'python') {
|
|
399
|
+
const importPattern = /^(?:from\s+(\S+)|import\s+(\S+))/gm;
|
|
400
|
+
let match;
|
|
401
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
402
|
+
imports.push((match[1] || match[2]).split('.')[0]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (language === 'javascript' || language === 'typescript') {
|
|
406
|
+
// ES6 imports
|
|
407
|
+
const es6Pattern = /import\s+.*?\s+from\s+['"](.+?)['"]/g;
|
|
408
|
+
let match;
|
|
409
|
+
while ((match = es6Pattern.exec(content)) !== null) {
|
|
410
|
+
imports.push(match[1]);
|
|
411
|
+
}
|
|
412
|
+
// CommonJS requires
|
|
413
|
+
const cjsPattern = /require\s*\(\s*['"](.+?)['"]\s*\)/g;
|
|
414
|
+
while ((match = cjsPattern.exec(content)) !== null) {
|
|
415
|
+
imports.push(match[1]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (language === 'rust') {
|
|
419
|
+
const usePattern = /use\s+(\w+(?:::\w+)*)/g;
|
|
420
|
+
let match;
|
|
421
|
+
while ((match = usePattern.exec(content)) !== null) {
|
|
422
|
+
imports.push(match[1]);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else if (language === 'go') {
|
|
426
|
+
const singlePattern = /import\s+"([^"]+)"/g;
|
|
427
|
+
let match;
|
|
428
|
+
while ((match = singlePattern.exec(content)) !== null) {
|
|
429
|
+
imports.push(match[1]);
|
|
430
|
+
}
|
|
431
|
+
const multiPattern = /import\s*\(([\s\S]*?)\)/g;
|
|
432
|
+
while ((match = multiPattern.exec(content)) !== null) {
|
|
433
|
+
const pkgPattern = /"([^"]+)"/g;
|
|
434
|
+
let pkgMatch;
|
|
435
|
+
while ((pkgMatch = pkgPattern.exec(match[1])) !== null) {
|
|
436
|
+
imports.push(pkgMatch[1]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// Ignore errors
|
|
443
|
+
}
|
|
444
|
+
return imports;
|
|
445
|
+
}
|
|
446
|
+
extractFunctions(filePath, language) {
|
|
447
|
+
const functions = [];
|
|
448
|
+
try {
|
|
449
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
450
|
+
if (language === 'python') {
|
|
451
|
+
const funcPattern = /^def\s+(\w+)\s*\(/gm;
|
|
452
|
+
let match;
|
|
453
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
454
|
+
functions.push(match[1]);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if (language === 'javascript' || language === 'typescript') {
|
|
458
|
+
// Function declarations
|
|
459
|
+
const funcPattern = /function\s+(\w+)\s*\(/g;
|
|
460
|
+
let match;
|
|
461
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
462
|
+
functions.push(match[1]);
|
|
463
|
+
}
|
|
464
|
+
// Arrow functions
|
|
465
|
+
const arrowPattern = /const\s+(\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/g;
|
|
466
|
+
while ((match = arrowPattern.exec(content)) !== null) {
|
|
467
|
+
functions.push(match[1]);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else if (language === 'rust') {
|
|
471
|
+
const fnPattern = /fn\s+(\w+)\s*[<(]/g;
|
|
472
|
+
let match;
|
|
473
|
+
while ((match = fnPattern.exec(content)) !== null) {
|
|
474
|
+
functions.push(match[1]);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Ignore errors
|
|
480
|
+
}
|
|
481
|
+
return functions;
|
|
482
|
+
}
|
|
483
|
+
extractClasses(filePath, language) {
|
|
484
|
+
const classes = [];
|
|
485
|
+
try {
|
|
486
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
487
|
+
if (language === 'python') {
|
|
488
|
+
const classPattern = /^class\s+(\w+)/gm;
|
|
489
|
+
let match;
|
|
490
|
+
while ((match = classPattern.exec(content)) !== null) {
|
|
491
|
+
classes.push(match[1]);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if (language === 'javascript' || language === 'typescript') {
|
|
495
|
+
const classPattern = /class\s+(\w+)/g;
|
|
496
|
+
let match;
|
|
497
|
+
while ((match = classPattern.exec(content)) !== null) {
|
|
498
|
+
classes.push(match[1]);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else if (language === 'rust') {
|
|
502
|
+
const structPattern = /struct\s+(\w+)/g;
|
|
503
|
+
const enumPattern = /enum\s+(\w+)/g;
|
|
504
|
+
let match;
|
|
505
|
+
while ((match = structPattern.exec(content)) !== null) {
|
|
506
|
+
classes.push(match[1]);
|
|
507
|
+
}
|
|
508
|
+
while ((match = enumPattern.exec(content)) !== null) {
|
|
509
|
+
classes.push(match[1]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// Ignore errors
|
|
515
|
+
}
|
|
516
|
+
return classes;
|
|
517
|
+
}
|
|
518
|
+
// ========================================================================
|
|
519
|
+
// TECHNOLOGY DETECTION
|
|
520
|
+
// ========================================================================
|
|
521
|
+
detectTechnologies() {
|
|
522
|
+
const configFiles = {
|
|
523
|
+
'package.json': ['nodejs', 'npm'],
|
|
524
|
+
'yarn.lock': ['yarn'],
|
|
525
|
+
'pnpm-lock.yaml': ['pnpm'],
|
|
526
|
+
'Cargo.toml': ['rust', 'cargo'],
|
|
527
|
+
'requirements.txt': ['python'],
|
|
528
|
+
'Pipfile': ['python', 'pipenv'],
|
|
529
|
+
'poetry.lock': ['python', 'poetry'],
|
|
530
|
+
'pyproject.toml': ['python'],
|
|
531
|
+
'go.mod': ['go'],
|
|
532
|
+
'pom.xml': ['java', 'maven'],
|
|
533
|
+
'build.gradle': ['java', 'gradle'],
|
|
534
|
+
'build.gradle.kts': ['kotlin', 'gradle'],
|
|
535
|
+
'composer.json': ['php', 'composer'],
|
|
536
|
+
'Gemfile': ['ruby', 'bundler'],
|
|
537
|
+
'Dockerfile': ['docker'],
|
|
538
|
+
'docker-compose.yml': ['docker', 'docker-compose'],
|
|
539
|
+
'docker-compose.yaml': ['docker', 'docker-compose'],
|
|
540
|
+
'kubernetes.yaml': ['kubernetes'],
|
|
541
|
+
'k8s.yaml': ['kubernetes'],
|
|
542
|
+
'terraform.tf': ['terraform'],
|
|
543
|
+
'Jenkinsfile': ['jenkins'],
|
|
544
|
+
'.gitlab-ci.yml': ['gitlab-ci'],
|
|
545
|
+
'.travis.yml': ['travis-ci'],
|
|
546
|
+
'azure-pipelines.yml': ['azure-pipelines'],
|
|
547
|
+
};
|
|
548
|
+
for (const [configFile, techs] of Object.entries(configFiles)) {
|
|
549
|
+
if (fs.existsSync(path.join(this.projectPath, configFile))) {
|
|
550
|
+
for (const tech of techs) {
|
|
551
|
+
this.technologies.add(tech);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Check for GitHub Actions
|
|
556
|
+
if (fs.existsSync(path.join(this.projectPath, '.github', 'workflows'))) {
|
|
557
|
+
this.technologies.add('github-actions');
|
|
558
|
+
}
|
|
559
|
+
// Check package.json for frameworks
|
|
560
|
+
this.detectNpmFrameworks();
|
|
561
|
+
// Check for Python frameworks
|
|
562
|
+
this.detectPythonFrameworks();
|
|
563
|
+
}
|
|
564
|
+
detectNpmFrameworks() {
|
|
565
|
+
const pkgJsonPath = path.join(this.projectPath, 'package.json');
|
|
566
|
+
if (!fs.existsSync(pkgJsonPath))
|
|
567
|
+
return;
|
|
568
|
+
try {
|
|
569
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
570
|
+
const deps = {
|
|
571
|
+
...pkg.dependencies,
|
|
572
|
+
...pkg.devDependencies,
|
|
573
|
+
};
|
|
574
|
+
const frameworkMap = {
|
|
575
|
+
'react': 'react',
|
|
576
|
+
'vue': 'vue',
|
|
577
|
+
'@angular/core': 'angular',
|
|
578
|
+
'svelte': 'svelte',
|
|
579
|
+
'next': 'nextjs',
|
|
580
|
+
'nuxt': 'nuxt',
|
|
581
|
+
'gatsby': 'gatsby',
|
|
582
|
+
'express': 'express',
|
|
583
|
+
'fastify': 'fastify',
|
|
584
|
+
'koa': 'koa',
|
|
585
|
+
'@nestjs/core': 'nestjs',
|
|
586
|
+
'electron': 'electron',
|
|
587
|
+
'@tauri-apps/api': 'tauri',
|
|
588
|
+
'vite': 'vite',
|
|
589
|
+
'webpack': 'webpack',
|
|
590
|
+
'typescript': 'typescript',
|
|
591
|
+
'jest': 'jest',
|
|
592
|
+
'vitest': 'vitest',
|
|
593
|
+
'cypress': 'cypress',
|
|
594
|
+
'playwright': 'playwright',
|
|
595
|
+
};
|
|
596
|
+
for (const [dep, tech] of Object.entries(frameworkMap)) {
|
|
597
|
+
if (dep in deps) {
|
|
598
|
+
this.technologies.add(tech);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
// Ignore errors
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
detectPythonFrameworks() {
|
|
607
|
+
if (!this.technologies.has('python'))
|
|
608
|
+
return;
|
|
609
|
+
const frameworkImports = {
|
|
610
|
+
'django': 'django',
|
|
611
|
+
'flask': 'flask',
|
|
612
|
+
'fastapi': 'fastapi',
|
|
613
|
+
'tornado': 'tornado',
|
|
614
|
+
'pyramid': 'pyramid',
|
|
615
|
+
'bottle': 'bottle',
|
|
616
|
+
'pytest': 'pytest',
|
|
617
|
+
'unittest': 'unittest',
|
|
618
|
+
};
|
|
619
|
+
for (const fileInfo of this.files.values()) {
|
|
620
|
+
if (fileInfo.language === 'python') {
|
|
621
|
+
for (const imp of fileInfo.imports) {
|
|
622
|
+
const baseImp = imp.split('.')[0];
|
|
623
|
+
if (frameworkImports[baseImp]) {
|
|
624
|
+
this.technologies.add(frameworkImports[baseImp]);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// ========================================================================
|
|
631
|
+
// DEPENDENCY ANALYSIS
|
|
632
|
+
// ========================================================================
|
|
633
|
+
analyzeDependencies() {
|
|
634
|
+
// Python dependencies
|
|
635
|
+
for (const file of ['requirements.txt', 'Pipfile', 'pyproject.toml']) {
|
|
636
|
+
const filePath = path.join(this.projectPath, file);
|
|
637
|
+
if (fs.existsSync(filePath)) {
|
|
638
|
+
this.dependencies['python'] = this.parsePythonDeps(filePath);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// Node dependencies
|
|
643
|
+
const pkgJson = path.join(this.projectPath, 'package.json');
|
|
644
|
+
if (fs.existsSync(pkgJson)) {
|
|
645
|
+
this.dependencies['npm'] = this.parseNpmDeps(pkgJson);
|
|
646
|
+
}
|
|
647
|
+
// Rust dependencies
|
|
648
|
+
const cargoToml = path.join(this.projectPath, 'Cargo.toml');
|
|
649
|
+
if (fs.existsSync(cargoToml)) {
|
|
650
|
+
this.dependencies['cargo'] = this.parseCargoDeps(cargoToml);
|
|
651
|
+
}
|
|
652
|
+
// Go dependencies
|
|
653
|
+
const goMod = path.join(this.projectPath, 'go.mod');
|
|
654
|
+
if (fs.existsSync(goMod)) {
|
|
655
|
+
this.dependencies['go'] = this.parseGoDeps(goMod);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
parsePythonDeps(filePath) {
|
|
659
|
+
const deps = [];
|
|
660
|
+
if (filePath.endsWith('requirements.txt')) {
|
|
661
|
+
try {
|
|
662
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
663
|
+
for (const line of content.split('\n')) {
|
|
664
|
+
const trimmed = line.trim();
|
|
665
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
666
|
+
const pkg = trimmed.split('==')[0].split('>=')[0].split('~=')[0].trim();
|
|
667
|
+
deps.push(pkg);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
// Ignore errors
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (filePath.endsWith('pyproject.toml')) {
|
|
676
|
+
try {
|
|
677
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
678
|
+
// Simple parsing for dependencies
|
|
679
|
+
const depMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
680
|
+
if (depMatch) {
|
|
681
|
+
const pkgPattern = /"([^">=<~!]+)/g;
|
|
682
|
+
let match;
|
|
683
|
+
while ((match = pkgPattern.exec(depMatch[1])) !== null) {
|
|
684
|
+
deps.push(match[1].trim());
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
// Ignore errors
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return deps;
|
|
693
|
+
}
|
|
694
|
+
parseNpmDeps(filePath) {
|
|
695
|
+
try {
|
|
696
|
+
const pkg = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
697
|
+
return Object.keys({
|
|
698
|
+
...pkg.dependencies,
|
|
699
|
+
...pkg.devDependencies,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return [];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
parseCargoDeps(filePath) {
|
|
707
|
+
try {
|
|
708
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
709
|
+
const deps = [];
|
|
710
|
+
const depSection = content.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/);
|
|
711
|
+
if (depSection) {
|
|
712
|
+
const lines = depSection[1].split('\n');
|
|
713
|
+
for (const line of lines) {
|
|
714
|
+
const match = line.match(/^(\w+)\s*=/);
|
|
715
|
+
if (match) {
|
|
716
|
+
deps.push(match[1]);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return deps;
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
parseGoDeps(filePath) {
|
|
727
|
+
const deps = [];
|
|
728
|
+
try {
|
|
729
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
730
|
+
const requirePattern = /require\s+([^\s]+)/g;
|
|
731
|
+
let match;
|
|
732
|
+
while ((match = requirePattern.exec(content)) !== null) {
|
|
733
|
+
deps.push(match[1]);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// Ignore errors
|
|
738
|
+
}
|
|
739
|
+
return deps;
|
|
740
|
+
}
|
|
741
|
+
// ========================================================================
|
|
742
|
+
// PROJECT STRUCTURE
|
|
743
|
+
// ========================================================================
|
|
744
|
+
getDirectoryStructure() {
|
|
745
|
+
const structure = {};
|
|
746
|
+
for (const filePath of this.files.keys()) {
|
|
747
|
+
const parts = filePath.split(path.sep);
|
|
748
|
+
let current = structure;
|
|
749
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
750
|
+
if (!(parts[i] in current)) {
|
|
751
|
+
current[parts[i]] = {};
|
|
752
|
+
}
|
|
753
|
+
current = current[parts[i]];
|
|
754
|
+
}
|
|
755
|
+
current[parts[parts.length - 1]] = 'file';
|
|
756
|
+
}
|
|
757
|
+
return structure;
|
|
758
|
+
}
|
|
759
|
+
findEntryPoints() {
|
|
760
|
+
const entryPoints = [];
|
|
761
|
+
const commonEntries = new Set([
|
|
762
|
+
'main.py', 'app.py', 'index.py', '__main__.py', 'run.py', 'wsgi.py',
|
|
763
|
+
'index.js', 'app.js', 'main.js', 'server.js', 'index.ts', 'main.ts',
|
|
764
|
+
'main.rs', 'lib.rs',
|
|
765
|
+
'main.go',
|
|
766
|
+
'Main.java', 'Application.java',
|
|
767
|
+
'Program.cs',
|
|
768
|
+
'index.html', 'index.php',
|
|
769
|
+
]);
|
|
770
|
+
for (const filePath of this.files.keys()) {
|
|
771
|
+
const fileName = path.basename(filePath);
|
|
772
|
+
if (commonEntries.has(fileName)) {
|
|
773
|
+
entryPoints.push(filePath);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return entryPoints;
|
|
777
|
+
}
|
|
778
|
+
findConfigFiles() {
|
|
779
|
+
const configFiles = [];
|
|
780
|
+
const configPatterns = [
|
|
781
|
+
'config', 'settings', '.env', 'environment',
|
|
782
|
+
'package.json', 'tsconfig.json', 'webpack.config',
|
|
783
|
+
'Cargo.toml', 'pom.xml', 'build.gradle',
|
|
784
|
+
'requirements.txt', 'setup.py', 'pyproject.toml',
|
|
785
|
+
'docker-compose', 'Dockerfile', '.gitignore',
|
|
786
|
+
'.eslintrc', '.prettierrc', 'babel.config',
|
|
787
|
+
];
|
|
788
|
+
for (const filePath of this.files.keys()) {
|
|
789
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
790
|
+
if (configPatterns.some(pattern => fileName.includes(pattern))) {
|
|
791
|
+
configFiles.push(filePath);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return configFiles;
|
|
795
|
+
}
|
|
796
|
+
findTestFiles() {
|
|
797
|
+
const testFiles = [];
|
|
798
|
+
const testPatterns = ['test', 'spec', '_test', '.test.', '.spec.', '__test__'];
|
|
799
|
+
const testDirs = ['test', 'tests', '__tests__'];
|
|
800
|
+
for (const filePath of this.files.keys()) {
|
|
801
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
802
|
+
const fileDir = path.dirname(filePath).toLowerCase();
|
|
803
|
+
if (testPatterns.some(pattern => fileName.includes(pattern))) {
|
|
804
|
+
testFiles.push(filePath);
|
|
805
|
+
}
|
|
806
|
+
else if (testDirs.some(pattern => fileDir.includes(pattern))) {
|
|
807
|
+
testFiles.push(filePath);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return testFiles;
|
|
811
|
+
}
|
|
812
|
+
// ========================================================================
|
|
813
|
+
// STATISTICS
|
|
814
|
+
// ========================================================================
|
|
815
|
+
getLanguageStats() {
|
|
816
|
+
const stats = {};
|
|
817
|
+
for (const fileInfo of this.files.values()) {
|
|
818
|
+
stats[fileInfo.language] = (stats[fileInfo.language] || 0) + 1;
|
|
819
|
+
}
|
|
820
|
+
return stats;
|
|
821
|
+
}
|
|
822
|
+
getProjectStatus() {
|
|
823
|
+
if (this.files.size === 0) {
|
|
824
|
+
this.scan();
|
|
825
|
+
}
|
|
826
|
+
const langStats = this.getLanguageStats();
|
|
827
|
+
const mainLanguage = Object.entries(langStats).sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
|
|
828
|
+
return {
|
|
829
|
+
totalFiles: this.files.size,
|
|
830
|
+
languages: Object.keys(langStats),
|
|
831
|
+
mainLanguage,
|
|
832
|
+
hasTests: Array.from(this.files.keys()).some(f => f.toLowerCase().includes('test')),
|
|
833
|
+
hasCi: ['github-actions', 'jenkins', 'gitlab-ci', 'travis-ci'].some(ci => this.technologies.has(ci)),
|
|
834
|
+
hasDocker: this.technologies.has('docker'),
|
|
835
|
+
isGitRepo: fs.existsSync(path.join(this.projectPath, '.git')),
|
|
836
|
+
technologies: Array.from(this.technologies),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
getStatistics() {
|
|
840
|
+
return this.stats;
|
|
841
|
+
}
|
|
842
|
+
getFiles() {
|
|
843
|
+
return this.files;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
// ============================================================================
|
|
847
|
+
// SINGLETON INSTANCE
|
|
848
|
+
// ============================================================================
|
|
849
|
+
let scannerInstance = null;
|
|
850
|
+
export function getProjectScanner(projectPath) {
|
|
851
|
+
if (!scannerInstance || (projectPath && scannerInstance['projectPath'] !== path.resolve(projectPath))) {
|
|
852
|
+
scannerInstance = new ProjectScanner(projectPath || process.cwd());
|
|
853
|
+
}
|
|
854
|
+
return scannerInstance;
|
|
855
|
+
}
|
|
856
|
+
export function resetProjectScanner() {
|
|
857
|
+
scannerInstance = null;
|
|
858
|
+
}
|
|
859
|
+
//# sourceMappingURL=projectScanner.js.map
|