baseguard 1.0.2 → 1.0.4

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.
Files changed (169) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +628 -613
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts +2 -0
  67. package/dist/core/startup-optimizer.d.ts.map +1 -1
  68. package/dist/core/startup-optimizer.js +19 -12
  69. package/dist/core/startup-optimizer.js.map +1 -1
  70. package/dist/core/system-error-handler.d.ts.map +1 -1
  71. package/dist/core/system-error-handler.js +18 -11
  72. package/dist/core/system-error-handler.js.map +1 -1
  73. package/dist/git/automation-engine.d.ts.map +1 -1
  74. package/dist/git/automation-engine.js +5 -4
  75. package/dist/git/automation-engine.js.map +1 -1
  76. package/dist/git/github-manager.d.ts.map +1 -1
  77. package/dist/git/github-manager.js.map +1 -1
  78. package/dist/git/hook-manager.js +5 -5
  79. package/dist/git/hook-manager.js.map +1 -1
  80. package/dist/parsers/parser-manager.d.ts.map +1 -1
  81. package/dist/parsers/parser-manager.js +1 -1
  82. package/dist/parsers/parser-manager.js.map +1 -1
  83. package/dist/parsers/svelte-parser.js +1 -1
  84. package/dist/parsers/svelte-parser.js.map +1 -1
  85. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  86. package/dist/parsers/vanilla-parser.js.map +1 -1
  87. package/dist/parsers/vue-parser.d.ts.map +1 -1
  88. package/dist/parsers/vue-parser.js.map +1 -1
  89. package/dist/ui/components.d.ts +1 -1
  90. package/dist/ui/components.d.ts.map +1 -1
  91. package/dist/ui/components.js +11 -11
  92. package/dist/ui/components.js.map +1 -1
  93. package/dist/ui/terminal-header.js +14 -14
  94. package/package.json +105 -105
  95. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  96. package/src/ai/agentkit-orchestrator.ts +533 -533
  97. package/src/ai/fix-manager.ts +362 -362
  98. package/src/ai/gemini-analyzer.ts +665 -671
  99. package/src/ai/gemini-code-fixer.ts +539 -540
  100. package/src/ai/index.ts +3 -3
  101. package/src/ai/jules-implementer.ts +504 -460
  102. package/src/ai/unified-code-fixer.ts +347 -347
  103. package/src/commands/automation.ts +343 -343
  104. package/src/commands/check.ts +298 -299
  105. package/src/commands/config.ts +584 -583
  106. package/src/commands/fix.ts +264 -238
  107. package/src/commands/index.ts +6 -6
  108. package/src/commands/init.ts +155 -155
  109. package/src/commands/status.ts +306 -306
  110. package/src/core/api-key-manager.ts +298 -298
  111. package/src/core/baseguard.ts +757 -756
  112. package/src/core/baseline-checker.ts +564 -563
  113. package/src/core/cache-manager.ts +271 -271
  114. package/src/core/configuration-recovery.ts +672 -673
  115. package/src/core/configuration.ts +595 -595
  116. package/src/core/debug-logger.ts +590 -590
  117. package/src/core/directory-filter.ts +420 -420
  118. package/src/core/error-handler.ts +518 -517
  119. package/src/core/file-processor.ts +337 -337
  120. package/src/core/gitignore-manager.ts +168 -168
  121. package/src/core/graceful-degradation-manager.ts +596 -596
  122. package/src/core/index.ts +16 -16
  123. package/src/core/lazy-loader.ts +317 -307
  124. package/src/core/memory-manager.ts +290 -295
  125. package/src/core/parser-worker.ts +33 -0
  126. package/src/core/startup-optimizer.ts +246 -243
  127. package/src/core/system-error-handler.ts +755 -750
  128. package/src/git/automation-engine.ts +361 -361
  129. package/src/git/github-manager.ts +190 -192
  130. package/src/git/hook-manager.ts +210 -210
  131. package/src/git/index.ts +3 -3
  132. package/src/index.ts +7 -7
  133. package/src/parsers/feature-validator.ts +558 -558
  134. package/src/parsers/index.ts +7 -7
  135. package/src/parsers/parser-manager.ts +418 -419
  136. package/src/parsers/parser.ts +25 -25
  137. package/src/parsers/react-parser-optimized.ts +160 -160
  138. package/src/parsers/react-parser.ts +358 -358
  139. package/src/parsers/svelte-parser.ts +510 -510
  140. package/src/parsers/vanilla-parser.ts +685 -686
  141. package/src/parsers/vue-parser.ts +476 -478
  142. package/src/types/index.ts +95 -95
  143. package/src/ui/components.ts +567 -567
  144. package/src/ui/help.ts +192 -192
  145. package/src/ui/index.ts +3 -3
  146. package/src/ui/prompts.ts +680 -680
  147. package/src/ui/terminal-header.ts +58 -58
  148. package/test-build.js +40 -40
  149. package/test-config-commands.js +55 -55
  150. package/test-header-simple.js +32 -32
  151. package/test-terminal-header.js +11 -11
  152. package/test-ui.js +28 -28
  153. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  154. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  155. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  156. package/tests/fixtures/react-project/package.json +13 -13
  157. package/tests/fixtures/react-project/src/App.css +75 -75
  158. package/tests/fixtures/react-project/src/App.tsx +76 -76
  159. package/tests/fixtures/svelte-project/package.json +10 -10
  160. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  161. package/tests/fixtures/vanilla-project/index.html +75 -75
  162. package/tests/fixtures/vanilla-project/script.js +330 -330
  163. package/tests/fixtures/vanilla-project/styles.css +358 -358
  164. package/tests/fixtures/vue-project/package.json +11 -11
  165. package/tests/fixtures/vue-project/src/App.vue +215 -215
  166. package/tsconfig.json +34 -34
  167. package/vitest.config.ts +11 -11
  168. package/dist/terminal-header.d.ts +0 -12
  169. package/dist/terminal-header.js +0 -45
@@ -1,419 +1,418 @@
1
- import type { DetectedFeature } from '../types/index.js';
2
- import { Parser } from './parser.js';
3
- import { FeatureValidator } from './feature-validator.js';
4
- import { LazyLoader } from '../core/lazy-loader.js';
5
- import { MemoryManager } from '../core/memory-manager.js';
6
- import { readFile, stat } from 'fs/promises';
7
- import { join } from 'path';
8
-
9
- /**
10
- * Parser manager that coordinates all parsers and provides concurrent processing
11
- * for large codebases with advanced web APIs using lazy loading
12
- */
13
- export class ParserManager {
14
- private parsers: Map<string, Parser> = new Map();
15
- private readonly validator: FeatureValidator;
16
- private readonly maxConcurrency: number;
17
- private initialized = false;
18
-
19
- constructor(maxConcurrency: number = 10) {
20
- this.validator = new FeatureValidator();
21
- this.maxConcurrency = maxConcurrency;
22
- // Don't initialize parsers immediately - use lazy loading
23
- }
24
-
25
- /**
26
- * Lazy load parsers only when needed
27
- */
28
- private async ensureParsersLoaded(): Promise<void> {
29
- if (this.initialized) {
30
- return;
31
- }
32
-
33
- try {
34
- // Dynamically import parsers to reduce startup time
35
- const [
36
- { ReactParser },
37
- { VueParser },
38
- { SvelteParser },
39
- { VanillaParser }
40
- ] = await Promise.all([
41
- import('./react-parser.js'),
42
- import('./vue-parser.js'),
43
- import('./svelte-parser.js'),
44
- import('./vanilla-parser.js')
45
- ]);
46
-
47
- this.parsers.set('react', new ReactParser());
48
- this.parsers.set('vue', new VueParser());
49
- this.parsers.set('svelte', new SvelteParser());
50
- this.parsers.set('vanilla', new VanillaParser());
51
-
52
- this.initialized = true;
53
- } catch (error) {
54
- console.warn('Failed to load some parsers:', error);
55
- this.initialized = true;
56
- }
57
- }
58
-
59
- /**
60
- * Get parser for file type with lazy loading
61
- */
62
- private async getParserForFile(filePath: string): Promise<Parser | null> {
63
- await this.ensureParsersLoaded();
64
-
65
- for (const parser of this.parsers.values()) {
66
- if (parser.canParse(filePath)) {
67
- return parser;
68
- }
69
- }
70
-
71
- return null;
72
- }
73
-
74
- /**
75
- * Parse multiple files concurrently with error handling
76
- */
77
- async parseFiles(filePaths: string[]): Promise<DetectedFeature[]> {
78
- const allFeatures: DetectedFeature[] = [];
79
-
80
- // Filter out unsupported files
81
- const supportedFiles = filePaths.filter(filePath => this.canParseFile(filePath));
82
-
83
- if (supportedFiles.length === 0) {
84
- return [];
85
- }
86
-
87
- // Process files in batches for better performance
88
- const batches = this.createBatches(supportedFiles, this.maxConcurrency);
89
-
90
- for (const batch of batches) {
91
- try {
92
- const batchResults = await Promise.allSettled(
93
- batch.map(filePath => this.parseFile(filePath))
94
- );
95
-
96
- batchResults.forEach((result, index) => {
97
- if (result.status === 'fulfilled') {
98
- allFeatures.push(...result.value);
99
- } else {
100
- console.warn(`Failed to parse ${batch[index]}: ${result.reason}`);
101
- }
102
- });
103
-
104
- // Small delay between batches to prevent overwhelming the system
105
- if (batches.length > 1) {
106
- await this.sleep(10);
107
- }
108
-
109
- } catch (error) {
110
- console.error(`Error processing batch: ${error instanceof Error ? error.message : 'Unknown error'}`);
111
- }
112
- }
113
-
114
- // Validate and filter features
115
- return this.validator.validateFeatures(allFeatures, this.maxConcurrency);
116
- }
117
-
118
- /**
119
- * Parse a single file with memory optimization
120
- */
121
- async parseFile(filePath: string): Promise<DetectedFeature[]> {
122
- try {
123
- // Find appropriate parser
124
- const parser = await this.getParserForFile(filePath);
125
- if (!parser) {
126
- return [];
127
- }
128
-
129
- // Check file size and use streaming for large files
130
- const fileStats = await stat(filePath);
131
-
132
- if (MemoryManager.shouldStream(fileStats.size)) {
133
- return await this.parseFileStreaming(filePath, parser);
134
- }
135
-
136
- // Read file content for smaller files
137
- const content = await readFile(filePath, 'utf-8');
138
-
139
- // Parse features
140
- const features = await parser.parseFeatures(content, filePath);
141
-
142
- // Add file path to features for tracking and optimize memory
143
- return features.map(feature => MemoryManager.optimizeObject({
144
- ...feature,
145
- file: filePath
146
- }));
147
-
148
- } catch (error) {
149
- // Graceful error handling for unsupported files and syntax errors
150
- if (error instanceof Error) {
151
- if (error.message.includes('ENOENT')) {
152
- console.warn(`File not found: ${filePath}`);
153
- } else if (error.message.includes('SyntaxError') || error.message.includes('Unexpected token')) {
154
- console.warn(`Syntax error in ${filePath}: ${error.message}`);
155
- } else {
156
- console.warn(`Error parsing ${filePath}: ${error.message}`);
157
- }
158
- } else {
159
- console.warn(`Unknown error parsing ${filePath}`);
160
- }
161
- return [];
162
- }
163
- }
164
-
165
- /**
166
- * Parse large files using streaming to reduce memory usage
167
- */
168
- private async parseFileStreaming(filePath: string, parser: Parser): Promise<DetectedFeature[]> {
169
- const features: DetectedFeature[] = [];
170
-
171
- await MemoryManager.readFileStreaming(filePath, async (chunk, startLine) => {
172
- try {
173
- const chunkFeatures = await parser.parseFeatures(chunk, filePath);
174
-
175
- // Adjust line numbers for chunk offset
176
- const adjustedFeatures = chunkFeatures.map(feature => ({
177
- ...feature,
178
- line: feature.line + startLine - 1,
179
- file: filePath
180
- }));
181
-
182
- features.push(...adjustedFeatures);
183
- } catch (error) {
184
- // Continue processing other chunks even if one fails
185
- console.warn(`Error parsing chunk in ${filePath}: ${error}`);
186
- }
187
- });
188
-
189
- return features;
190
- }
191
-
192
- /**
193
- * Check if a file can be parsed by any available parser
194
- */
195
- async canParseFile(filePath: string): Promise<boolean> {
196
- await this.ensureParsersLoaded();
197
-
198
- for (const parser of this.parsers.values()) {
199
- if (parser.canParse(filePath)) {
200
- return true;
201
- }
202
- }
203
-
204
- return false;
205
- }
206
-
207
- /**
208
- * Get all supported file extensions
209
- */
210
- async getSupportedExtensions(): Promise<string[]> {
211
- await this.ensureParsersLoaded();
212
-
213
- const extensions = new Set<string>();
214
- this.parsers.forEach(parser => {
215
- parser.getSupportedExtensions().forEach(ext => extensions.add(ext));
216
- });
217
- return Array.from(extensions);
218
- }
219
-
220
- /**
221
- * Get parser information for debugging
222
- */
223
- async getParserInfo(): Promise<Array<{ name: string; extensions: string[] }>> {
224
- await this.ensureParsersLoaded();
225
-
226
- return Array.from(this.parsers.values()).map(parser => ({
227
- name: parser.getName(),
228
- extensions: parser.getSupportedExtensions()
229
- }));
230
- }
231
-
232
- /**
233
- * Filter files by supported extensions
234
- */
235
- async filterSupportedFiles(filePaths: string[]): Promise<string[]> {
236
- const supportedExtensions = await this.getSupportedExtensions();
237
- return filePaths.filter(filePath => {
238
- const extension = this.getFileExtension(filePath);
239
- return supportedExtensions.includes(extension);
240
- });
241
- }
242
-
243
- /**
244
- * Scan directory for supported files (recursive)
245
- */
246
- async scanDirectory(
247
- directoryPath: string,
248
- options: {
249
- recursive?: boolean;
250
- excludePatterns?: string[];
251
- maxDepth?: number;
252
- } = {}
253
- ): Promise<string[]> {
254
- const {
255
- recursive = true,
256
- excludePatterns = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt'],
257
- maxDepth = 10
258
- } = options;
259
-
260
- const supportedFiles: string[] = [];
261
-
262
- try {
263
- await this.scanDirectoryRecursive(
264
- directoryPath,
265
- supportedFiles,
266
- excludePatterns,
267
- recursive,
268
- 0,
269
- maxDepth
270
- );
271
- } catch (error) {
272
- console.error(`Error scanning directory ${directoryPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
273
- }
274
-
275
- return supportedFiles;
276
- }
277
-
278
- /**
279
- * Recursive directory scanning helper
280
- */
281
- private async scanDirectoryRecursive(
282
- currentPath: string,
283
- results: string[],
284
- excludePatterns: string[],
285
- recursive: boolean,
286
- currentDepth: number,
287
- maxDepth: number
288
- ): Promise<void> {
289
- if (currentDepth >= maxDepth) {
290
- return;
291
- }
292
-
293
- try {
294
- const { readdir, stat } = await import('fs/promises');
295
- const entries = await readdir(currentPath);
296
-
297
- for (const entry of entries) {
298
- const fullPath = join(currentPath, entry);
299
-
300
- // Skip excluded patterns
301
- if (excludePatterns.some(pattern => entry.includes(pattern))) {
302
- continue;
303
- }
304
-
305
- try {
306
- const stats = await stat(fullPath);
307
-
308
- if (stats.isFile()) {
309
- if (await this.canParseFile(fullPath)) {
310
- results.push(fullPath);
311
- }
312
- } else if (stats.isDirectory() && recursive) {
313
- await this.scanDirectoryRecursive(
314
- fullPath,
315
- results,
316
- excludePatterns,
317
- recursive,
318
- currentDepth + 1,
319
- maxDepth
320
- );
321
- }
322
- } catch (error) {
323
- // Skip files/directories that can't be accessed
324
- continue;
325
- }
326
- }
327
- } catch (error) {
328
- console.warn(`Could not read directory ${currentPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
329
- }
330
- }
331
-
332
- /**
333
- * Get processing statistics
334
- */
335
- async getProcessingStats(filePaths: string[]): Promise<{
336
- totalFiles: number;
337
- supportedFiles: number;
338
- filesByType: Record<string, number>;
339
- estimatedProcessingTime: number;
340
- memoryEstimate: string;
341
- }> {
342
- const supportedFiles = await this.filterSupportedFiles(filePaths);
343
- const filesByType: Record<string, number> = {};
344
-
345
- supportedFiles.forEach(filePath => {
346
- const extension = this.getFileExtension(filePath);
347
- filesByType[extension] = (filesByType[extension] || 0) + 1;
348
- });
349
-
350
- // Rough estimation: 10ms per file on average
351
- const estimatedProcessingTime = Math.ceil(supportedFiles.length * 10 / this.maxConcurrency);
352
-
353
- // Memory estimate: ~50KB per file on average
354
- const memoryEstimate = `${Math.round(supportedFiles.length * 50 / 1024)}MB`;
355
-
356
- return {
357
- totalFiles: filePaths.length,
358
- supportedFiles: supportedFiles.length,
359
- filesByType,
360
- estimatedProcessingTime,
361
- memoryEstimate
362
- };
363
- }
364
-
365
- /**
366
- * Get memory and performance statistics
367
- */
368
- getStats(): {
369
- parsersLoaded: number;
370
- maxConcurrency: number;
371
- memoryStats: any;
372
- } {
373
- return {
374
- parsersLoaded: this.parsers.size,
375
- maxConcurrency: this.maxConcurrency,
376
- memoryStats: MemoryManager.getMemoryStats()
377
- };
378
- }
379
-
380
- /**
381
- * Create batches for concurrent processing
382
- */
383
- private createBatches<T>(items: T[], batchSize: number): T[][] {
384
- const batches: T[][] = [];
385
- for (let i = 0; i < items.length; i += batchSize) {
386
- batches.push(items.slice(i, i + batchSize));
387
- }
388
- return batches;
389
- }
390
-
391
- /**
392
- * Get file extension
393
- */
394
- private getFileExtension(filePath: string): string {
395
- const match = filePath.match(/\.[^.]+$/);
396
- return match ? match[0] : '';
397
- }
398
-
399
- /**
400
- * Sleep utility for batch processing
401
- */
402
- private sleep(ms: number): Promise<void> {
403
- return new Promise(resolve => setTimeout(resolve, ms));
404
- }
405
-
406
- /**
407
- * Get feature validator instance
408
- */
409
- getValidator(): FeatureValidator {
410
- return this.validator;
411
- }
412
-
413
- /**
414
- * Validate features without parsing (for external use)
415
- */
416
- async validateFeatures(features: DetectedFeature[]): Promise<DetectedFeature[]> {
417
- return this.validator.validateFeatures(features, this.maxConcurrency);
418
- }
419
- }
1
+ import type { DetectedFeature } from '../types/index.js';
2
+ import { Parser } from './parser.js';
3
+ import { FeatureValidator } from './feature-validator.js';
4
+ import { MemoryManager } from '../core/memory-manager.js';
5
+ import { readFile, stat } from 'fs/promises';
6
+ import { join } from 'path';
7
+
8
+ /**
9
+ * Parser manager that coordinates all parsers and provides concurrent processing
10
+ * for large codebases with advanced web APIs using lazy loading
11
+ */
12
+ export class ParserManager {
13
+ private parsers: Map<string, Parser> = new Map();
14
+ private readonly validator: FeatureValidator;
15
+ private readonly maxConcurrency: number;
16
+ private initialized = false;
17
+
18
+ constructor(maxConcurrency: number = 10) {
19
+ this.validator = new FeatureValidator();
20
+ this.maxConcurrency = maxConcurrency;
21
+ // Don't initialize parsers immediately - use lazy loading
22
+ }
23
+
24
+ /**
25
+ * Lazy load parsers only when needed
26
+ */
27
+ private async ensureParsersLoaded(): Promise<void> {
28
+ if (this.initialized) {
29
+ return;
30
+ }
31
+
32
+ try {
33
+ // Dynamically import parsers to reduce startup time
34
+ const [
35
+ { ReactParser },
36
+ { VueParser },
37
+ { SvelteParser },
38
+ { VanillaParser }
39
+ ] = await Promise.all([
40
+ import('./react-parser.js'),
41
+ import('./vue-parser.js'),
42
+ import('./svelte-parser.js'),
43
+ import('./vanilla-parser.js')
44
+ ]);
45
+
46
+ this.parsers.set('react', new ReactParser());
47
+ this.parsers.set('vue', new VueParser());
48
+ this.parsers.set('svelte', new SvelteParser());
49
+ this.parsers.set('vanilla', new VanillaParser());
50
+
51
+ this.initialized = true;
52
+ } catch (error) {
53
+ console.warn('Failed to load some parsers:', error);
54
+ this.initialized = true;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get parser for file type with lazy loading
60
+ */
61
+ private async getParserForFile(filePath: string): Promise<Parser | null> {
62
+ await this.ensureParsersLoaded();
63
+
64
+ for (const parser of this.parsers.values()) {
65
+ if (parser.canParse(filePath)) {
66
+ return parser;
67
+ }
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * Parse multiple files concurrently with error handling
75
+ */
76
+ async parseFiles(filePaths: string[]): Promise<DetectedFeature[]> {
77
+ const allFeatures: DetectedFeature[] = [];
78
+
79
+ // Filter out unsupported files
80
+ const supportedFiles = await this.filterSupportedFiles(filePaths);
81
+
82
+ if (supportedFiles.length === 0) {
83
+ return [];
84
+ }
85
+
86
+ // Process files in batches for better performance
87
+ const batches = this.createBatches(supportedFiles, this.maxConcurrency);
88
+
89
+ for (const batch of batches) {
90
+ try {
91
+ const batchResults = await Promise.allSettled(
92
+ batch.map(filePath => this.parseFile(filePath))
93
+ );
94
+
95
+ batchResults.forEach((result, index) => {
96
+ if (result.status === 'fulfilled') {
97
+ allFeatures.push(...result.value);
98
+ } else {
99
+ console.warn(`Failed to parse ${batch[index]}: ${result.reason}`);
100
+ }
101
+ });
102
+
103
+ // Small delay between batches to prevent overwhelming the system
104
+ if (batches.length > 1) {
105
+ await this.sleep(10);
106
+ }
107
+
108
+ } catch (error) {
109
+ console.error(`Error processing batch: ${error instanceof Error ? error.message : 'Unknown error'}`);
110
+ }
111
+ }
112
+
113
+ // Validate and filter features
114
+ return this.validator.validateFeatures(allFeatures, this.maxConcurrency);
115
+ }
116
+
117
+ /**
118
+ * Parse a single file with memory optimization
119
+ */
120
+ async parseFile(filePath: string): Promise<DetectedFeature[]> {
121
+ try {
122
+ // Find appropriate parser
123
+ const parser = await this.getParserForFile(filePath);
124
+ if (!parser) {
125
+ return [];
126
+ }
127
+
128
+ // Check file size and use streaming for large files
129
+ const fileStats = await stat(filePath);
130
+
131
+ if (MemoryManager.shouldStream(fileStats.size)) {
132
+ return await this.parseFileStreaming(filePath, parser);
133
+ }
134
+
135
+ // Read file content for smaller files
136
+ const content = await readFile(filePath, 'utf-8');
137
+
138
+ // Parse features
139
+ const features = await parser.parseFeatures(content, filePath);
140
+
141
+ // Add file path to features for tracking and optimize memory
142
+ return features.map(feature => MemoryManager.optimizeObject({
143
+ ...feature,
144
+ file: filePath
145
+ }));
146
+
147
+ } catch (error) {
148
+ // Graceful error handling for unsupported files and syntax errors
149
+ if (error instanceof Error) {
150
+ if (error.message.includes('ENOENT')) {
151
+ console.warn(`File not found: ${filePath}`);
152
+ } else if (error.message.includes('SyntaxError') || error.message.includes('Unexpected token')) {
153
+ console.warn(`Syntax error in ${filePath}: ${error.message}`);
154
+ } else {
155
+ console.warn(`Error parsing ${filePath}: ${error.message}`);
156
+ }
157
+ } else {
158
+ console.warn(`Unknown error parsing ${filePath}`);
159
+ }
160
+ return [];
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Parse large files using streaming to reduce memory usage
166
+ */
167
+ private async parseFileStreaming(filePath: string, parser: Parser): Promise<DetectedFeature[]> {
168
+ const features: DetectedFeature[] = [];
169
+
170
+ await MemoryManager.readFileStreaming(filePath, async (chunk, startLine) => {
171
+ try {
172
+ const chunkFeatures = await parser.parseFeatures(chunk, filePath);
173
+
174
+ // Adjust line numbers for chunk offset
175
+ const adjustedFeatures = chunkFeatures.map(feature => ({
176
+ ...feature,
177
+ line: feature.line + startLine - 1,
178
+ file: filePath
179
+ }));
180
+
181
+ features.push(...adjustedFeatures);
182
+ } catch (error) {
183
+ // Continue processing other chunks even if one fails
184
+ console.warn(`Error parsing chunk in ${filePath}: ${error}`);
185
+ }
186
+ });
187
+
188
+ return features;
189
+ }
190
+
191
+ /**
192
+ * Check if a file can be parsed by any available parser
193
+ */
194
+ async canParseFile(filePath: string): Promise<boolean> {
195
+ await this.ensureParsersLoaded();
196
+
197
+ for (const parser of this.parsers.values()) {
198
+ if (parser.canParse(filePath)) {
199
+ return true;
200
+ }
201
+ }
202
+
203
+ return false;
204
+ }
205
+
206
+ /**
207
+ * Get all supported file extensions
208
+ */
209
+ async getSupportedExtensions(): Promise<string[]> {
210
+ await this.ensureParsersLoaded();
211
+
212
+ const extensions = new Set<string>();
213
+ this.parsers.forEach(parser => {
214
+ parser.getSupportedExtensions().forEach(ext => extensions.add(ext));
215
+ });
216
+ return Array.from(extensions);
217
+ }
218
+
219
+ /**
220
+ * Get parser information for debugging
221
+ */
222
+ async getParserInfo(): Promise<Array<{ name: string; extensions: string[] }>> {
223
+ await this.ensureParsersLoaded();
224
+
225
+ return Array.from(this.parsers.values()).map(parser => ({
226
+ name: parser.getName(),
227
+ extensions: parser.getSupportedExtensions()
228
+ }));
229
+ }
230
+
231
+ /**
232
+ * Filter files by supported extensions
233
+ */
234
+ async filterSupportedFiles(filePaths: string[]): Promise<string[]> {
235
+ const supportedExtensions = await this.getSupportedExtensions();
236
+ return filePaths.filter(filePath => {
237
+ const extension = this.getFileExtension(filePath);
238
+ return supportedExtensions.includes(extension);
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Scan directory for supported files (recursive)
244
+ */
245
+ async scanDirectory(
246
+ directoryPath: string,
247
+ options: {
248
+ recursive?: boolean;
249
+ excludePatterns?: string[];
250
+ maxDepth?: number;
251
+ } = {}
252
+ ): Promise<string[]> {
253
+ const {
254
+ recursive = true,
255
+ excludePatterns = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt'],
256
+ maxDepth = 10
257
+ } = options;
258
+
259
+ const supportedFiles: string[] = [];
260
+
261
+ try {
262
+ await this.scanDirectoryRecursive(
263
+ directoryPath,
264
+ supportedFiles,
265
+ excludePatterns,
266
+ recursive,
267
+ 0,
268
+ maxDepth
269
+ );
270
+ } catch (error) {
271
+ console.error(`Error scanning directory ${directoryPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
272
+ }
273
+
274
+ return supportedFiles;
275
+ }
276
+
277
+ /**
278
+ * Recursive directory scanning helper
279
+ */
280
+ private async scanDirectoryRecursive(
281
+ currentPath: string,
282
+ results: string[],
283
+ excludePatterns: string[],
284
+ recursive: boolean,
285
+ currentDepth: number,
286
+ maxDepth: number
287
+ ): Promise<void> {
288
+ if (currentDepth >= maxDepth) {
289
+ return;
290
+ }
291
+
292
+ try {
293
+ const { readdir, stat } = await import('fs/promises');
294
+ const entries = await readdir(currentPath);
295
+
296
+ for (const entry of entries) {
297
+ const fullPath = join(currentPath, entry);
298
+
299
+ // Skip excluded patterns
300
+ if (excludePatterns.some(pattern => entry.includes(pattern))) {
301
+ continue;
302
+ }
303
+
304
+ try {
305
+ const stats = await stat(fullPath);
306
+
307
+ if (stats.isFile()) {
308
+ if (await this.canParseFile(fullPath)) {
309
+ results.push(fullPath);
310
+ }
311
+ } else if (stats.isDirectory() && recursive) {
312
+ await this.scanDirectoryRecursive(
313
+ fullPath,
314
+ results,
315
+ excludePatterns,
316
+ recursive,
317
+ currentDepth + 1,
318
+ maxDepth
319
+ );
320
+ }
321
+ } catch (error) {
322
+ // Skip files/directories that can't be accessed
323
+ continue;
324
+ }
325
+ }
326
+ } catch (error) {
327
+ console.warn(`Could not read directory ${currentPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Get processing statistics
333
+ */
334
+ async getProcessingStats(filePaths: string[]): Promise<{
335
+ totalFiles: number;
336
+ supportedFiles: number;
337
+ filesByType: Record<string, number>;
338
+ estimatedProcessingTime: number;
339
+ memoryEstimate: string;
340
+ }> {
341
+ const supportedFiles = await this.filterSupportedFiles(filePaths);
342
+ const filesByType: Record<string, number> = {};
343
+
344
+ supportedFiles.forEach(filePath => {
345
+ const extension = this.getFileExtension(filePath);
346
+ filesByType[extension] = (filesByType[extension] || 0) + 1;
347
+ });
348
+
349
+ // Rough estimation: 10ms per file on average
350
+ const estimatedProcessingTime = Math.ceil(supportedFiles.length * 10 / this.maxConcurrency);
351
+
352
+ // Memory estimate: ~50KB per file on average
353
+ const memoryEstimate = `${Math.round(supportedFiles.length * 50 / 1024)}MB`;
354
+
355
+ return {
356
+ totalFiles: filePaths.length,
357
+ supportedFiles: supportedFiles.length,
358
+ filesByType,
359
+ estimatedProcessingTime,
360
+ memoryEstimate
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Get memory and performance statistics
366
+ */
367
+ getStats(): {
368
+ parsersLoaded: number;
369
+ maxConcurrency: number;
370
+ memoryStats: any;
371
+ } {
372
+ return {
373
+ parsersLoaded: this.parsers.size,
374
+ maxConcurrency: this.maxConcurrency,
375
+ memoryStats: MemoryManager.getMemoryStats()
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Create batches for concurrent processing
381
+ */
382
+ private createBatches<T>(items: T[], batchSize: number): T[][] {
383
+ const batches: T[][] = [];
384
+ for (let i = 0; i < items.length; i += batchSize) {
385
+ batches.push(items.slice(i, i + batchSize));
386
+ }
387
+ return batches;
388
+ }
389
+
390
+ /**
391
+ * Get file extension
392
+ */
393
+ private getFileExtension(filePath: string): string {
394
+ const match = filePath.match(/\.[^.]+$/);
395
+ return match ? match[0] : '';
396
+ }
397
+
398
+ /**
399
+ * Sleep utility for batch processing
400
+ */
401
+ private sleep(ms: number): Promise<void> {
402
+ return new Promise(resolve => setTimeout(resolve, ms));
403
+ }
404
+
405
+ /**
406
+ * Get feature validator instance
407
+ */
408
+ getValidator(): FeatureValidator {
409
+ return this.validator;
410
+ }
411
+
412
+ /**
413
+ * Validate features without parsing (for external use)
414
+ */
415
+ async validateFeatures(features: DetectedFeature[]): Promise<DetectedFeature[]> {
416
+ return this.validator.validateFeatures(features, this.maxConcurrency);
417
+ }
418
+ }