devbonzai 1.6.8 → 1.7.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.
Files changed (2) hide show
  1. package/cli.js +624 -35
  2. package/package.json +3 -2
package/cli.js CHANGED
@@ -65,6 +65,12 @@ const cors = require('./node_modules/cors');
65
65
  const fs = require('fs');
66
66
  const path = require('path');
67
67
  const { exec, spawn } = require('child_process');
68
+ let babelParser = null;
69
+ try {
70
+ babelParser = require('./node_modules/@babel/parser');
71
+ } catch (e) {
72
+ // Babel parser not available, will fall back gracefully
73
+ }
68
74
 
69
75
  const app = express();
70
76
  const ROOT = path.join(__dirname, '..');
@@ -137,14 +143,17 @@ function shouldIgnore(relativePath, ignorePatterns) {
137
143
  return ignorePatterns.some(pattern => pattern.test(relativePath));
138
144
  }
139
145
 
140
- // Extract top-level functions from a Python file
146
+ // Extract functions, classes, and methods from a Python file
141
147
  function extractPythonFunctions(filePath) {
142
148
  try {
143
149
  const content = fs.readFileSync(filePath, 'utf8');
144
150
  const lines = content.split('\\n');
145
151
  const functions = [];
152
+ const classes = [];
146
153
  let currentFunction = null;
154
+ let currentClass = null;
147
155
  let decorators = [];
156
+ let classIndent = -1;
148
157
 
149
158
  for (let i = 0; i < lines.length; i++) {
150
159
  const line = lines[i];
@@ -154,20 +163,102 @@ function extractPythonFunctions(filePath) {
154
163
  const match = line.match(/^\\s*/);
155
164
  const currentIndent = match ? match[0].length : 0;
156
165
 
157
- // Check for decorators (only at top level, before function)
166
+ // Check for decorators (only at top level, before function/class)
158
167
  if (trimmed.startsWith('@') && currentIndent === 0) {
159
168
  decorators.push(line);
160
169
  continue;
161
170
  }
162
171
 
172
+ // Check if this is a top-level class definition
173
+ const classMatch = trimmed.match(/^class\\s+(\\w+)/);
174
+ if (classMatch && currentIndent === 0) {
175
+ // Save previous function/class if exists
176
+ if (currentFunction) {
177
+ currentFunction.content = currentFunction.content.trim();
178
+ functions.push(currentFunction);
179
+ currentFunction = null;
180
+ }
181
+ if (currentClass) {
182
+ currentClass.content = currentClass.content.trim();
183
+ classes.push(currentClass);
184
+ }
185
+
186
+ // Start new class
187
+ const className = classMatch[1];
188
+ let classContent = '';
189
+
190
+ // Add decorators if any
191
+ if (decorators.length > 0) {
192
+ classContent = decorators.join('\\n') + '\\n';
193
+ decorators = [];
194
+ }
195
+
196
+ classContent += line;
197
+ classIndent = currentIndent;
198
+
199
+ currentClass = {
200
+ name: className,
201
+ content: classContent,
202
+ methods: [],
203
+ startLine: i + 1,
204
+ endLine: i + 1
205
+ };
206
+ continue;
207
+ }
208
+
209
+ // Check if this is a method definition (inside a class)
210
+ const methodMatch = trimmed.match(/^def\\s+(\\w+)\\s*\\(/);
211
+ if (methodMatch && currentClass && currentIndent > classIndent) {
212
+ // Save previous method if exists
213
+ if (currentFunction) {
214
+ currentFunction.content = currentFunction.content.trim();
215
+ currentClass.methods.push(currentFunction);
216
+ currentFunction = null;
217
+ }
218
+
219
+ // Start new method
220
+ const methodName = methodMatch[1];
221
+ let methodContent = '';
222
+
223
+ // Add decorators if any
224
+ if (decorators.length > 0) {
225
+ methodContent = decorators.join('\\n') + '\\n';
226
+ decorators = [];
227
+ }
228
+
229
+ methodContent += line;
230
+
231
+ currentFunction = {
232
+ name: currentClass.name + '.' + methodName,
233
+ content: methodContent,
234
+ startLine: i + 1,
235
+ endLine: i + 1,
236
+ isMethod: true,
237
+ className: currentClass.name,
238
+ methodName: methodName
239
+ };
240
+ continue;
241
+ }
242
+
163
243
  // Check if this is a top-level function definition
164
244
  const funcMatch = trimmed.match(/^def\\s+(\\w+)\\s*\\(/);
165
245
 
166
246
  if (funcMatch && currentIndent === 0) {
167
- // Save previous function if exists
247
+ // Save previous function/class if exists
168
248
  if (currentFunction) {
169
249
  currentFunction.content = currentFunction.content.trim();
250
+ if (currentFunction.isMethod && currentClass) {
251
+ currentClass.methods.push(currentFunction);
252
+ } else {
170
253
  functions.push(currentFunction);
254
+ }
255
+ currentFunction = null;
256
+ }
257
+ if (currentClass) {
258
+ currentClass.content = currentClass.content.trim();
259
+ classes.push(currentClass);
260
+ currentClass = null;
261
+ classIndent = -1;
171
262
  }
172
263
 
173
264
  // Start new function
@@ -188,15 +279,27 @@ function extractPythonFunctions(filePath) {
188
279
  startLine: i + 1,
189
280
  endLine: i + 1
190
281
  };
191
- } else if (currentFunction) {
192
- // We're processing lines after a function definition
282
+ } else if (currentFunction || currentClass) {
283
+ // We're processing lines after a function/class definition
193
284
  if (currentIndent === 0 && trimmed && !trimmed.startsWith('#')) {
194
- // Back to top level with non-comment content - function ended
285
+ // Back to top level with non-comment content - function/class ended
286
+ if (currentFunction) {
195
287
  currentFunction.content = currentFunction.content.trim();
288
+ if (currentFunction.isMethod && currentClass) {
289
+ currentClass.methods.push(currentFunction);
290
+ } else {
196
291
  functions.push(currentFunction);
292
+ }
197
293
  currentFunction = null;
294
+ }
295
+ if (currentClass) {
296
+ currentClass.content = currentClass.content.trim();
297
+ classes.push(currentClass);
298
+ currentClass = null;
299
+ classIndent = -1;
300
+ }
198
301
 
199
- // Check if this line starts a new function
302
+ // Check if this line starts a new function/class
200
303
  if (funcMatch) {
201
304
  const functionName = funcMatch[1];
202
305
  let functionContent = '';
@@ -214,28 +317,421 @@ function extractPythonFunctions(filePath) {
214
317
  startLine: i + 1,
215
318
  endLine: i + 1
216
319
  };
320
+ } else if (classMatch) {
321
+ const className = classMatch[1];
322
+ let classContent = '';
323
+
324
+ if (decorators.length > 0) {
325
+ classContent = decorators.join('\\n') + '\\n';
326
+ decorators = [];
327
+ }
328
+
329
+ classContent += line;
330
+ classIndent = currentIndent;
331
+
332
+ currentClass = {
333
+ name: className,
334
+ content: classContent,
335
+ methods: [],
336
+ startLine: i + 1,
337
+ endLine: i + 1
338
+ };
217
339
  } else if (trimmed.startsWith('@')) {
218
340
  decorators.push(line);
219
341
  }
220
342
  } else {
221
- // Still inside function (indented or empty/comment line)
343
+ // Still inside function/class (indented or empty/comment line)
344
+ if (currentFunction) {
222
345
  currentFunction.content += '\\n' + line;
223
346
  currentFunction.endLine = i + 1;
347
+ }
348
+ if (currentClass) {
349
+ currentClass.content += '\\n' + line;
350
+ currentClass.endLine = i + 1;
351
+ }
224
352
  }
225
353
  }
226
354
  }
227
355
 
228
- // Don't forget the last function
356
+ // Don't forget the last function/class
229
357
  if (currentFunction) {
230
358
  currentFunction.content = currentFunction.content.trim();
359
+ if (currentFunction.isMethod && currentClass) {
360
+ currentClass.methods.push(currentFunction);
361
+ } else {
231
362
  functions.push(currentFunction);
363
+ }
364
+ }
365
+ if (currentClass) {
366
+ currentClass.content = currentClass.content.trim();
367
+ classes.push(currentClass);
232
368
  }
233
369
 
234
- return functions;
370
+ return { functions, classes };
235
371
  } catch (e) {
236
- // If parsing fails (invalid Python, etc.), return empty array
372
+ // If parsing fails (invalid Python, etc.), return empty arrays
237
373
  console.warn('Failed to parse Python file:', filePath, e.message);
238
- return [];
374
+ return { functions: [], classes: [] };
375
+ }
376
+ }
377
+
378
+ // Extract functions, classes, and methods from a JavaScript/TypeScript file
379
+ function extractJavaScriptFunctions(filePath) {
380
+ try {
381
+ if (!babelParser) {
382
+ return { functions: [], classes: [] };
383
+ }
384
+
385
+ // Skip .d.ts files, minified files, and node_modules
386
+ if (filePath.endsWith('.d.ts') || filePath.endsWith('.min.js') || filePath.includes('node_modules')) {
387
+ return { functions: [], classes: [] };
388
+ }
389
+
390
+ const content = fs.readFileSync(filePath, 'utf8');
391
+ const functions = [];
392
+ const classes = [];
393
+
394
+ // Determine if it's TypeScript
395
+ const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
396
+
397
+ try {
398
+ const ast = babelParser.parse(content, {
399
+ sourceType: 'module',
400
+ plugins: [
401
+ 'typescript',
402
+ 'jsx',
403
+ 'decorators-legacy',
404
+ 'classProperties',
405
+ 'objectRestSpread',
406
+ 'asyncGenerators',
407
+ 'functionBind',
408
+ 'exportDefaultFrom',
409
+ 'exportNamespaceFrom',
410
+ 'dynamicImport',
411
+ 'nullishCoalescingOperator',
412
+ 'optionalChaining'
413
+ ]
414
+ });
415
+
416
+ // Helper to extract code snippet from source
417
+ const getCode = (node) => {
418
+ return content.substring(node.start, node.end);
419
+ };
420
+
421
+ // Traverse AST
422
+ function traverse(node) {
423
+ if (!node) return;
424
+
425
+ // Function declarations: function myFunc() {}
426
+ if (node.type === 'FunctionDeclaration' && node.id) {
427
+ functions.push({
428
+ name: node.id.name,
429
+ content: getCode(node),
430
+ startLine: node.loc ? node.loc.start.line : 0,
431
+ endLine: node.loc ? node.loc.end.line : 0
432
+ });
433
+ }
434
+
435
+ // Arrow functions: const myFunc = () => {}
436
+ if (node.type === 'VariableDeclarator' &&
437
+ node.init &&
438
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') &&
439
+ node.id && node.id.type === 'Identifier') {
440
+ const funcNode = node.init;
441
+ const funcContent = getCode(node);
442
+ functions.push({
443
+ name: node.id.name,
444
+ content: funcContent,
445
+ startLine: node.loc ? node.loc.start.line : 0,
446
+ endLine: node.loc ? node.loc.end.line : 0
447
+ });
448
+ }
449
+
450
+ // Class declarations: class User { ... }
451
+ if (node.type === 'ClassDeclaration' && node.id) {
452
+ const className = node.id.name;
453
+ const methods = [];
454
+
455
+ // Extract methods
456
+ if (node.body && node.body.body) {
457
+ for (const member of node.body.body) {
458
+ if (member.type === 'MethodDefinition' && member.key) {
459
+ const methodName = member.key.type === 'Identifier' ? member.key.name :
460
+ member.key.type === 'PrivateName' ? '#' + member.key.id.name :
461
+ String(member.key.value || member.key.name);
462
+ methods.push({
463
+ name: className + '.' + methodName,
464
+ content: getCode(member),
465
+ startLine: member.loc ? member.loc.start.line : 0,
466
+ endLine: member.loc ? member.loc.end.line : 0,
467
+ isMethod: true,
468
+ className: className,
469
+ methodName: methodName
470
+ });
471
+ }
472
+ }
473
+ }
474
+
475
+ classes.push({
476
+ name: className,
477
+ content: getCode(node),
478
+ methods: methods,
479
+ startLine: node.loc ? node.loc.start.line : 0,
480
+ endLine: node.loc ? node.loc.end.line : 0
481
+ });
482
+ }
483
+
484
+ // Export declarations: export function, export default
485
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
486
+ if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
487
+ functions.push({
488
+ name: node.declaration.id.name,
489
+ content: getCode(node.declaration),
490
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
491
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
492
+ isExported: true
493
+ });
494
+ } else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
495
+ const className = node.declaration.id.name;
496
+ const methods = [];
497
+
498
+ if (node.declaration.body && node.declaration.body.body) {
499
+ for (const member of node.declaration.body.body) {
500
+ if (member.type === 'MethodDefinition' && member.key) {
501
+ const methodName = member.key.type === 'Identifier' ? member.key.name :
502
+ member.key.type === 'PrivateName' ? '#' + member.key.id.name :
503
+ String(member.key.value || member.key.name);
504
+ methods.push({
505
+ name: className + '.' + methodName,
506
+ content: getCode(member),
507
+ startLine: member.loc ? member.loc.start.line : 0,
508
+ endLine: member.loc ? member.loc.end.line : 0,
509
+ isMethod: true,
510
+ className: className,
511
+ methodName: methodName
512
+ });
513
+ }
514
+ }
515
+ }
516
+
517
+ classes.push({
518
+ name: className,
519
+ content: getCode(node.declaration),
520
+ methods: methods,
521
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
522
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
523
+ isExported: true
524
+ });
525
+ }
526
+ }
527
+
528
+ // Recursively traverse children
529
+ for (const key in node) {
530
+ if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
531
+ const child = node[key];
532
+ if (Array.isArray(child)) {
533
+ child.forEach(traverse);
534
+ } else if (child && typeof child === 'object' && child.type) {
535
+ traverse(child);
536
+ }
537
+ }
538
+ }
539
+
540
+ traverse(ast);
541
+ } catch (parseError) {
542
+ // Silently skip parsing errors - these are expected for some files
543
+ // Only log if it's not in node_modules or a known problematic file type
544
+ if (!filePath.includes('node_modules') && !filePath.endsWith('.d.ts') && !filePath.endsWith('.min.js')) {
545
+ // Suppress warnings for common parsing issues
546
+ const errorMsg = parseError.message || '';
547
+ if (!errorMsg.includes('outside of function') &&
548
+ !errorMsg.includes('Missing initializer') &&
549
+ !errorMsg.includes('Export') &&
550
+ !errorMsg.includes('Unexpected token')) {
551
+ // Only log unexpected errors
552
+ }
553
+ }
554
+ return { functions: [], classes: [] };
555
+ }
556
+
557
+ return { functions, classes };
558
+ } catch (e) {
559
+ console.warn('Failed to read JavaScript/TypeScript file:', filePath, e.message);
560
+ return { functions: [], classes: [] };
561
+ }
562
+ }
563
+
564
+ // Extract script content from Vue file and parse it
565
+ function extractVueFunctions(filePath) {
566
+ try {
567
+ const content = fs.readFileSync(filePath, 'utf8');
568
+
569
+ // Extract <script> section from Vue file
570
+ const scriptMatch = content.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/);
571
+ if (!scriptMatch) {
572
+ return { functions: [], classes: [] };
573
+ }
574
+
575
+ const scriptContent = scriptMatch[1];
576
+
577
+ // Create a temporary file path for parsing (just for reference)
578
+ // Parse the script content as JavaScript/TypeScript
579
+ if (!babelParser) {
580
+ return { functions: [], classes: [] };
581
+ }
582
+
583
+ const functions = [];
584
+ const classes = [];
585
+
586
+ // Check if it's TypeScript
587
+ const isTypeScript = scriptMatch[0].includes('lang="ts"') || scriptMatch[0].includes("lang='ts'");
588
+
589
+ try {
590
+ const ast = babelParser.parse(scriptContent, {
591
+ sourceType: 'module',
592
+ plugins: [
593
+ 'typescript',
594
+ 'jsx',
595
+ 'decorators-legacy',
596
+ 'classProperties',
597
+ 'objectRestSpread',
598
+ 'asyncGenerators',
599
+ 'functionBind',
600
+ 'exportDefaultFrom',
601
+ 'exportNamespaceFrom',
602
+ 'dynamicImport',
603
+ 'nullishCoalescingOperator',
604
+ 'optionalChaining'
605
+ ]
606
+ });
607
+
608
+ // Helper to extract code snippet from source
609
+ const getCode = (node) => {
610
+ return scriptContent.substring(node.start, node.end);
611
+ };
612
+
613
+ // Traverse AST (same logic as JavaScript parser)
614
+ function traverse(node) {
615
+ if (!node) return;
616
+
617
+ if (node.type === 'FunctionDeclaration' && node.id) {
618
+ functions.push({
619
+ name: node.id.name,
620
+ content: getCode(node),
621
+ startLine: node.loc ? node.loc.start.line : 0,
622
+ endLine: node.loc ? node.loc.end.line : 0
623
+ });
624
+ }
625
+
626
+ if (node.type === 'VariableDeclarator' &&
627
+ node.init &&
628
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') &&
629
+ node.id && node.id.type === 'Identifier') {
630
+ const funcContent = getCode(node);
631
+ functions.push({
632
+ name: node.id.name,
633
+ content: funcContent,
634
+ startLine: node.loc ? node.loc.start.line : 0,
635
+ endLine: node.loc ? node.loc.end.line : 0
636
+ });
637
+ }
638
+
639
+ if (node.type === 'ClassDeclaration' && node.id) {
640
+ const className = node.id.name;
641
+ const methods = [];
642
+
643
+ if (node.body && node.body.body) {
644
+ for (const member of node.body.body) {
645
+ if (member.type === 'MethodDefinition' && member.key) {
646
+ const methodName = member.key.type === 'Identifier' ? member.key.name :
647
+ member.key.type === 'PrivateName' ? '#' + member.key.id.name :
648
+ String(member.key.value || member.key.name);
649
+ methods.push({
650
+ name: className + '.' + methodName,
651
+ content: getCode(member),
652
+ startLine: member.loc ? member.loc.start.line : 0,
653
+ endLine: member.loc ? member.loc.end.line : 0,
654
+ isMethod: true,
655
+ className: className,
656
+ methodName: methodName
657
+ });
658
+ }
659
+ }
660
+ }
661
+
662
+ classes.push({
663
+ name: className,
664
+ content: getCode(node),
665
+ methods: methods,
666
+ startLine: node.loc ? node.loc.start.line : 0,
667
+ endLine: node.loc ? node.loc.end.line : 0
668
+ });
669
+ }
670
+
671
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
672
+ if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
673
+ functions.push({
674
+ name: node.declaration.id.name,
675
+ content: getCode(node.declaration),
676
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
677
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
678
+ isExported: true
679
+ });
680
+ } else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
681
+ const className = node.declaration.id.name;
682
+ const methods = [];
683
+
684
+ if (node.declaration.body && node.declaration.body.body) {
685
+ for (const member of node.declaration.body.body) {
686
+ if (member.type === 'MethodDefinition' && member.key) {
687
+ const methodName = member.key.type === 'Identifier' ? member.key.name :
688
+ member.key.type === 'PrivateName' ? '#' + member.key.id.name :
689
+ String(member.key.value || member.key.name);
690
+ methods.push({
691
+ name: className + '.' + methodName,
692
+ content: getCode(member),
693
+ startLine: member.loc ? member.loc.start.line : 0,
694
+ endLine: member.loc ? member.loc.end.line : 0,
695
+ isMethod: true,
696
+ className: className,
697
+ methodName: methodName
698
+ });
699
+ }
700
+ }
701
+ }
702
+
703
+ classes.push({
704
+ name: className,
705
+ content: getCode(node.declaration),
706
+ methods: methods,
707
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
708
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
709
+ isExported: true
710
+ });
711
+ }
712
+ }
713
+
714
+ for (const key in node) {
715
+ if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
716
+ const child = node[key];
717
+ if (Array.isArray(child)) {
718
+ child.forEach(traverse);
719
+ } else if (child && typeof child === 'object' && child.type) {
720
+ traverse(child);
721
+ }
722
+ }
723
+ }
724
+
725
+ traverse(ast);
726
+ } catch (parseError) {
727
+ // Silently skip parsing errors for Vue files
728
+ return { functions: [], classes: [] };
729
+ }
730
+
731
+ return { functions, classes };
732
+ } catch (e) {
733
+ console.warn('Failed to read Vue file:', filePath, e.message);
734
+ return { functions: [], classes: [] };
239
735
  }
240
736
  }
241
737
 
@@ -259,23 +755,65 @@ function listAllFiles(dir, base = '', ignorePatterns = null) {
259
755
 
260
756
  const stat = fs.statSync(fullPath);
261
757
  if (stat && stat.isDirectory()) {
758
+ // Skip node_modules directories explicitly
759
+ if (file === 'node_modules' || relativePath.includes('node_modules/')) {
760
+ continue;
761
+ }
262
762
  // Add the directory itself to results
263
763
  results.push(relativePath + '/');
264
764
  // Recursively list files inside the directory
265
765
  results = results.concat(listAllFiles(fullPath, relativePath, ignorePatterns));
266
766
  } else {
767
+ // Skip files in node_modules explicitly
768
+ if (relativePath.includes('node_modules/') || fullPath.includes('node_modules')) {
769
+ continue;
770
+ }
771
+
267
772
  results.push(relativePath);
268
773
 
269
- // If this is a Python file, extract functions and add them as virtual files
270
- if (file.endsWith('.py')) {
271
- const functions = extractPythonFunctions(fullPath);
272
- for (const func of functions) {
273
- // Add function file as downstream from the Python file
274
- // Format: <python_file_path>/<function_name.function>
774
+ // Helper function to add functions, classes, and methods as virtual files
775
+ const addVirtualFiles = (parseResult, filePath) => {
776
+ // Add functions
777
+ for (const func of parseResult.functions) {
275
778
  const functionFileName = func.name + '.function';
276
- const functionFilePath = relativePath + '/' + functionFileName;
779
+ const functionFilePath = filePath + '/' + functionFileName;
277
780
  results.push(functionFilePath);
278
781
  }
782
+
783
+ // Add classes and their methods
784
+ for (const cls of parseResult.classes) {
785
+ // Add class itself (optional, but useful)
786
+ const className = cls.name + '.class';
787
+ const classFilePath = filePath + '/' + className;
788
+ results.push(classFilePath);
789
+
790
+ // Add methods with dot notation: ClassName.methodName
791
+ for (const method of cls.methods) {
792
+ const methodFileName = method.name + '.method';
793
+ const methodFilePath = filePath + '/' + methodFileName;
794
+ results.push(methodFilePath);
795
+ }
796
+ }
797
+ };
798
+
799
+ // Handle Python files
800
+ if (file.endsWith('.py')) {
801
+ const parseResult = extractPythonFunctions(fullPath);
802
+ addVirtualFiles(parseResult, relativePath);
803
+ }
804
+
805
+ // Handle JavaScript/TypeScript files
806
+ // Skip .d.ts files (TypeScript declaration files) and .min.js files (minified)
807
+ if ((file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.ts') || file.endsWith('.tsx')) &&
808
+ !file.endsWith('.d.ts') && !file.endsWith('.min.js')) {
809
+ const parseResult = extractJavaScriptFunctions(fullPath);
810
+ addVirtualFiles(parseResult, relativePath);
811
+ }
812
+
813
+ // Handle Vue files
814
+ if (file.endsWith('.vue')) {
815
+ const parseResult = extractVueFunctions(fullPath);
816
+ addVirtualFiles(parseResult, relativePath);
279
817
  }
280
818
  }
281
819
  }
@@ -301,30 +839,80 @@ app.get('/read', (req, res) => {
301
839
  return res.status(400).send('Invalid path');
302
840
  }
303
841
 
304
- // Check if this is a .function file request
305
- if (requestedPath.endsWith('.function')) {
306
- // Extract function name and Python file path
307
- // Path format: <python_file_path>/<function_name.function>
308
- const functionFileName = path.basename(requestedPath, '.function');
309
- const pythonFilePath = path.dirname(filePath);
842
+ // Helper function to find and return content from parse result
843
+ const findAndReturn = (parseResult, name, type) => {
844
+ if (type === 'function') {
845
+ const target = parseResult.functions.find(f => f.name === name);
846
+ if (target) return target.content;
847
+ } else if (type === 'method') {
848
+ // Method name format: ClassName.methodName
849
+ for (const cls of parseResult.classes) {
850
+ const method = cls.methods.find(m => m.name === name);
851
+ if (method) return method.content;
852
+ }
853
+ } else if (type === 'class') {
854
+ const target = parseResult.classes.find(c => c.name === name);
855
+ if (target) return target.content;
856
+ }
857
+ return null;
858
+ };
859
+
860
+ // Check if this is a virtual file request (.function, .method, or .class)
861
+ if (requestedPath.endsWith('.function') || requestedPath.endsWith('.method') || requestedPath.endsWith('.class')) {
862
+ const parentFilePath = path.dirname(filePath);
863
+ const parentRelativePath = path.relative(ROOT, parentFilePath);
864
+ const fileName = path.basename(parentRelativePath);
310
865
 
311
- // Check if the Python file exists
866
+ // Determine file type and parser
867
+ let parseResult = null;
868
+ let parser = null;
869
+
870
+ if (fileName.endsWith('.py')) {
871
+ parser = extractPythonFunctions;
872
+ } else if (fileName.endsWith('.js') || fileName.endsWith('.jsx') || fileName.endsWith('.ts') || fileName.endsWith('.tsx')) {
873
+ parser = extractJavaScriptFunctions;
874
+ } else if (fileName.endsWith('.vue')) {
875
+ parser = extractVueFunctions;
876
+ } else {
877
+ return res.status(404).send('Parent file type not supported');
878
+ }
879
+
880
+ // Check if the parent file exists
312
881
  try {
313
- if (!fs.existsSync(pythonFilePath) || !pythonFilePath.endsWith('.py')) {
314
- return res.status(404).send('Parent Python file not found');
882
+ if (!fs.existsSync(parentFilePath)) {
883
+ return res.status(404).send('Parent file not found');
884
+ }
885
+
886
+ // Parse the file
887
+ parseResult = parser(parentFilePath);
888
+
889
+ // Extract the requested item name
890
+ let itemName = '';
891
+ let itemType = '';
892
+
893
+ if (requestedPath.endsWith('.function')) {
894
+ itemName = path.basename(requestedPath, '.function');
895
+ itemType = 'function';
896
+ } else if (requestedPath.endsWith('.method')) {
897
+ itemName = path.basename(requestedPath, '.method');
898
+ itemType = 'method';
899
+ } else if (requestedPath.endsWith('.class')) {
900
+ itemName = path.basename(requestedPath, '.class');
901
+ itemType = 'class';
315
902
  }
316
903
 
317
- // Extract functions from the Python file
318
- const functions = extractPythonFunctions(pythonFilePath);
319
- const targetFunction = functions.find(f => f.name === functionFileName);
904
+ // Find and return the content
905
+ const content = findAndReturn(parseResult, itemName, itemType);
320
906
 
321
- if (!targetFunction) {
322
- return res.status(404).send('Function not found in Python file');
907
+ if (!content) {
908
+ return res.status(404).send(\`\${itemType} '\${itemName}' not found in file\`);
323
909
  }
324
910
 
325
- return res.json({ content: targetFunction.content });
911
+ return res.json({ content });
326
912
  } catch (e) {
327
- return res.status(500).send('Error reading function: ' + e.message);
913
+ const errorType = requestedPath.endsWith('.function') ? 'function' :
914
+ requestedPath.endsWith('.method') ? 'method' : 'class';
915
+ return res.status(500).send('Error reading ' + errorType + ': ' + e.message);
328
916
  }
329
917
  }
330
918
 
@@ -736,6 +1324,7 @@ async function main() {
736
1324
  packageJson.dependencies.cors = "^2.8.5";
737
1325
  packageJson.dependencies["body-parser"] = "^1.20.2";
738
1326
  packageJson.dependencies["raw-body"] = "^2.5.2";
1327
+ packageJson.dependencies["@babel/parser"] = "^7.23.0";
739
1328
 
740
1329
  // Add script to run receiver
741
1330
  if (!packageJson.scripts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devbonzai",
3
- "version": "1.6.8",
3
+ "version": "1.7.0",
4
4
  "description": "Quickly set up a local file server in any repository for browser-based file access",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "express": "^4.18.2",
22
22
  "cors": "^2.8.5",
23
23
  "body-parser": "^1.20.2",
24
- "raw-body": "^2.5.2"
24
+ "raw-body": "^2.5.2",
25
+ "@babel/parser": "^7.23.0"
25
26
  }
26
27
  }