devbonzai 1.8.1 → 2.0.2
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/README.md +1 -69
- package/cli.js +162 -472
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -13,72 +13,4 @@ npm publish
|
|
|
13
13
|
- node cli.js
|
|
14
14
|
- this will setup a receiver in this directory
|
|
15
15
|
|
|
16
|
-
Used for Bonzai's linking functionality from web to local development environment.
|
|
17
|
-
|
|
18
|
-
## API Endpoints
|
|
19
|
-
|
|
20
|
-
The server exposes several endpoints for file operations and code analysis:
|
|
21
|
-
|
|
22
|
-
### Import Validation
|
|
23
|
-
|
|
24
|
-
**POST /validate-imports** - Validate imports in a single file
|
|
25
|
-
|
|
26
|
-
Request body:
|
|
27
|
-
```json
|
|
28
|
-
{
|
|
29
|
-
"filePath": "src/components/Button.js",
|
|
30
|
-
"imports": [
|
|
31
|
-
{
|
|
32
|
-
"path": "./utils",
|
|
33
|
-
"line": 5,
|
|
34
|
-
"symbols": [
|
|
35
|
-
{ "name": "formatDate", "isDefault": false },
|
|
36
|
-
{ "name": "default", "isDefault": true }
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Response:
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"file": "src/components/Button.js",
|
|
47
|
-
"errors": [
|
|
48
|
-
{
|
|
49
|
-
"line": 5,
|
|
50
|
-
"import": "./utils",
|
|
51
|
-
"symbol": "formatDate",
|
|
52
|
-
"error": "'formatDate' is not exported from './utils'"
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
"errorCount": 1
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**POST /validate-imports-batch** - Validate imports for multiple files
|
|
60
|
-
|
|
61
|
-
Request body:
|
|
62
|
-
```json
|
|
63
|
-
{
|
|
64
|
-
"files": [
|
|
65
|
-
{
|
|
66
|
-
"filePath": "src/components/Button.js",
|
|
67
|
-
"imports": [...]
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"filePath": "src/components/Input.js",
|
|
71
|
-
"imports": [...]
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Response: Array of validation results (same format as single file endpoint)
|
|
78
|
-
|
|
79
|
-
### Supported Features
|
|
80
|
-
|
|
81
|
-
- **JavaScript/TypeScript**: Validates default exports, named exports, and namespace imports
|
|
82
|
-
- **Python**: Validates function, class, and variable imports
|
|
83
|
-
- **File Resolution**: Automatically resolves relative imports and checks for files with common extensions (.js, .jsx, .ts, .tsx, .py, index files)
|
|
84
|
-
- **External Packages**: Skips validation for node_modules imports
|
|
16
|
+
Used for Bonzai's linking functionality from web to local development environment.
|
package/cli.js
CHANGED
|
@@ -92,8 +92,6 @@ app.get('/', (req, res) => {
|
|
|
92
92
|
'POST /move': 'Move file or folder (body: {source, destination})',
|
|
93
93
|
'POST /open-cursor': 'Open Cursor (body: {path, line?})',
|
|
94
94
|
'POST /prompt_agent': 'Execute cursor-agent command (body: {prompt})',
|
|
95
|
-
'POST /validate-imports': 'Validate imports in a file (body: {filePath, imports})',
|
|
96
|
-
'POST /validate-imports-batch': 'Validate imports for multiple files (body: {files})',
|
|
97
95
|
'POST /shutdown': 'Gracefully shutdown the server'
|
|
98
96
|
},
|
|
99
97
|
example: 'Try: /list or /read?path=README.md'
|
|
@@ -456,19 +454,41 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
456
454
|
});
|
|
457
455
|
}
|
|
458
456
|
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
if (node.type === 'ClassDeclaration' && node.id && parentType !== 'ExportNamedDeclaration') {
|
|
462
|
-
const className = node.id.name;
|
|
457
|
+
// Helper function to extract methods from a class body
|
|
458
|
+
const extractClassMethods = (classNode, className) => {
|
|
463
459
|
const methods = [];
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
460
|
+
if (classNode.body && classNode.body.body && Array.isArray(classNode.body.body)) {
|
|
461
|
+
for (const member of classNode.body.body) {
|
|
462
|
+
// Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
|
|
463
|
+
if (member && member.type === 'MethodDefinition' && member.key) {
|
|
464
|
+
let methodName;
|
|
465
|
+
if (member.key.type === 'Identifier') {
|
|
466
|
+
methodName = member.key.name;
|
|
467
|
+
} else if (member.key.type === 'PrivateName') {
|
|
468
|
+
methodName = '#' + member.key.id.name;
|
|
469
|
+
} else if (member.key.type === 'StringLiteral' || member.key.type === 'NumericLiteral') {
|
|
470
|
+
methodName = String(member.key.value);
|
|
471
|
+
} else {
|
|
472
|
+
methodName = String(member.key.value || member.key.name || 'unknown');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Include kind (constructor, get, set, method) in the name for clarity
|
|
476
|
+
const kind = member.kind || 'method';
|
|
477
|
+
const isStatic = member.static || false;
|
|
478
|
+
|
|
479
|
+
// For getters and setters, include the kind in the method name to distinguish them
|
|
480
|
+
// e.g., "value" getter vs "value" setter -> "get value" and "set value"
|
|
481
|
+
let fullMethodName = methodName;
|
|
482
|
+
if (kind === 'get') {
|
|
483
|
+
fullMethodName = 'get ' + methodName;
|
|
484
|
+
} else if (kind === 'set') {
|
|
485
|
+
fullMethodName = 'set ' + methodName;
|
|
486
|
+
} else if (kind === 'constructor') {
|
|
487
|
+
fullMethodName = 'constructor';
|
|
488
|
+
} else if (isStatic) {
|
|
489
|
+
fullMethodName = 'static ' + methodName;
|
|
490
|
+
}
|
|
491
|
+
|
|
472
492
|
methods.push({
|
|
473
493
|
name: className + '.' + methodName,
|
|
474
494
|
content: getCode(member),
|
|
@@ -476,11 +496,22 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
476
496
|
endLine: member.loc ? member.loc.end.line : 0,
|
|
477
497
|
isMethod: true,
|
|
478
498
|
className: className,
|
|
479
|
-
methodName: methodName
|
|
499
|
+
methodName: methodName,
|
|
500
|
+
kind: kind,
|
|
501
|
+
static: isStatic
|
|
480
502
|
});
|
|
481
503
|
}
|
|
482
504
|
}
|
|
483
505
|
}
|
|
506
|
+
return methods;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Class declarations: class User { ... }
|
|
510
|
+
// Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
|
|
511
|
+
if (node.type === 'ClassDeclaration' && node.id &&
|
|
512
|
+
parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
|
|
513
|
+
const className = node.id.name;
|
|
514
|
+
const methods = extractClassMethods(node, className);
|
|
484
515
|
|
|
485
516
|
classes.push({
|
|
486
517
|
name: className,
|
|
@@ -491,7 +522,7 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
491
522
|
});
|
|
492
523
|
}
|
|
493
524
|
|
|
494
|
-
// Export declarations: export function, export
|
|
525
|
+
// Export declarations: export function, export class
|
|
495
526
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
496
527
|
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
497
528
|
functions.push({
|
|
@@ -505,26 +536,7 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
505
536
|
visitedNodes.add(node.declaration);
|
|
506
537
|
} else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
507
538
|
const className = node.declaration.id.name;
|
|
508
|
-
const methods =
|
|
509
|
-
|
|
510
|
-
if (node.declaration.body && node.declaration.body.body) {
|
|
511
|
-
for (const member of node.declaration.body.body) {
|
|
512
|
-
if (member.type === 'MethodDefinition' && member.key) {
|
|
513
|
-
const methodName = member.key.type === 'Identifier' ? member.key.name :
|
|
514
|
-
member.key.type === 'PrivateName' ? '#' + member.key.id.name :
|
|
515
|
-
String(member.key.value || member.key.name);
|
|
516
|
-
methods.push({
|
|
517
|
-
name: className + '.' + methodName,
|
|
518
|
-
content: getCode(member),
|
|
519
|
-
startLine: member.loc ? member.loc.start.line : 0,
|
|
520
|
-
endLine: member.loc ? member.loc.end.line : 0,
|
|
521
|
-
isMethod: true,
|
|
522
|
-
className: className,
|
|
523
|
-
methodName: methodName
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
539
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
528
540
|
|
|
529
541
|
classes.push({
|
|
530
542
|
name: className,
|
|
@@ -539,6 +551,36 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
539
551
|
}
|
|
540
552
|
}
|
|
541
553
|
|
|
554
|
+
// Export default declarations: export default class
|
|
555
|
+
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
|
|
556
|
+
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
557
|
+
const className = node.declaration.id.name;
|
|
558
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
559
|
+
|
|
560
|
+
classes.push({
|
|
561
|
+
name: className,
|
|
562
|
+
content: getCode(node.declaration),
|
|
563
|
+
methods: methods,
|
|
564
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
565
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
566
|
+
isExported: true,
|
|
567
|
+
isDefaultExport: true
|
|
568
|
+
});
|
|
569
|
+
// Mark as visited to avoid duplicate processing
|
|
570
|
+
visitedNodes.add(node.declaration);
|
|
571
|
+
} else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
572
|
+
functions.push({
|
|
573
|
+
name: node.declaration.id.name,
|
|
574
|
+
content: getCode(node.declaration),
|
|
575
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
576
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
577
|
+
isExported: true,
|
|
578
|
+
isDefaultExport: true
|
|
579
|
+
});
|
|
580
|
+
visitedNodes.add(node.declaration);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
542
584
|
// Recursively traverse children
|
|
543
585
|
for (const key in node) {
|
|
544
586
|
if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
|
|
@@ -660,18 +702,41 @@ function extractVueFunctions(filePath) {
|
|
|
660
702
|
});
|
|
661
703
|
}
|
|
662
704
|
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
if (node.type === 'ClassDeclaration' && node.id && parentType !== 'ExportNamedDeclaration') {
|
|
666
|
-
const className = node.id.name;
|
|
705
|
+
// Helper function to extract methods from a class body
|
|
706
|
+
const extractClassMethods = (classNode, className) => {
|
|
667
707
|
const methods = [];
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (member.type === 'MethodDefinition' && member.key) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
708
|
+
if (classNode.body && classNode.body.body && Array.isArray(classNode.body.body)) {
|
|
709
|
+
for (const member of classNode.body.body) {
|
|
710
|
+
// Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
|
|
711
|
+
if (member && member.type === 'MethodDefinition' && member.key) {
|
|
712
|
+
let methodName;
|
|
713
|
+
if (member.key.type === 'Identifier') {
|
|
714
|
+
methodName = member.key.name;
|
|
715
|
+
} else if (member.key.type === 'PrivateName') {
|
|
716
|
+
methodName = '#' + member.key.id.name;
|
|
717
|
+
} else if (member.key.type === 'StringLiteral' || member.key.type === 'NumericLiteral') {
|
|
718
|
+
methodName = String(member.key.value);
|
|
719
|
+
} else {
|
|
720
|
+
methodName = String(member.key.value || member.key.name || 'unknown');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Include kind (constructor, get, set, method) in the name for clarity
|
|
724
|
+
const kind = member.kind || 'method';
|
|
725
|
+
const isStatic = member.static || false;
|
|
726
|
+
|
|
727
|
+
// For getters and setters, include the kind in the method name to distinguish them
|
|
728
|
+
// e.g., "value" getter vs "value" setter -> "get value" and "set value"
|
|
729
|
+
let fullMethodName = methodName;
|
|
730
|
+
if (kind === 'get') {
|
|
731
|
+
fullMethodName = 'get ' + methodName;
|
|
732
|
+
} else if (kind === 'set') {
|
|
733
|
+
fullMethodName = 'set ' + methodName;
|
|
734
|
+
} else if (kind === 'constructor') {
|
|
735
|
+
fullMethodName = 'constructor';
|
|
736
|
+
} else if (isStatic) {
|
|
737
|
+
fullMethodName = 'static ' + methodName;
|
|
738
|
+
}
|
|
739
|
+
|
|
675
740
|
methods.push({
|
|
676
741
|
name: className + '.' + methodName,
|
|
677
742
|
content: getCode(member),
|
|
@@ -679,11 +744,22 @@ function extractVueFunctions(filePath) {
|
|
|
679
744
|
endLine: member.loc ? member.loc.end.line : 0,
|
|
680
745
|
isMethod: true,
|
|
681
746
|
className: className,
|
|
682
|
-
methodName: methodName
|
|
747
|
+
methodName: methodName,
|
|
748
|
+
kind: kind,
|
|
749
|
+
static: isStatic
|
|
683
750
|
});
|
|
684
751
|
}
|
|
685
752
|
}
|
|
686
753
|
}
|
|
754
|
+
return methods;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// Class declarations: class User { ... }
|
|
758
|
+
// Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
|
|
759
|
+
if (node.type === 'ClassDeclaration' && node.id &&
|
|
760
|
+
parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
|
|
761
|
+
const className = node.id.name;
|
|
762
|
+
const methods = extractClassMethods(node, className);
|
|
687
763
|
|
|
688
764
|
classes.push({
|
|
689
765
|
name: className,
|
|
@@ -694,7 +770,7 @@ function extractVueFunctions(filePath) {
|
|
|
694
770
|
});
|
|
695
771
|
}
|
|
696
772
|
|
|
697
|
-
// Export declarations: export function, export
|
|
773
|
+
// Export declarations: export function, export class
|
|
698
774
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
699
775
|
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
700
776
|
functions.push({
|
|
@@ -708,26 +784,7 @@ function extractVueFunctions(filePath) {
|
|
|
708
784
|
visitedNodes.add(node.declaration);
|
|
709
785
|
} else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
710
786
|
const className = node.declaration.id.name;
|
|
711
|
-
const methods =
|
|
712
|
-
|
|
713
|
-
if (node.declaration.body && node.declaration.body.body) {
|
|
714
|
-
for (const member of node.declaration.body.body) {
|
|
715
|
-
if (member.type === 'MethodDefinition' && member.key) {
|
|
716
|
-
const methodName = member.key.type === 'Identifier' ? member.key.name :
|
|
717
|
-
member.key.type === 'PrivateName' ? '#' + member.key.id.name :
|
|
718
|
-
String(member.key.value || member.key.name);
|
|
719
|
-
methods.push({
|
|
720
|
-
name: className + '.' + methodName,
|
|
721
|
-
content: getCode(member),
|
|
722
|
-
startLine: member.loc ? member.loc.start.line : 0,
|
|
723
|
-
endLine: member.loc ? member.loc.end.line : 0,
|
|
724
|
-
isMethod: true,
|
|
725
|
-
className: className,
|
|
726
|
-
methodName: methodName
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
787
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
731
788
|
|
|
732
789
|
classes.push({
|
|
733
790
|
name: className,
|
|
@@ -742,6 +799,36 @@ function extractVueFunctions(filePath) {
|
|
|
742
799
|
}
|
|
743
800
|
}
|
|
744
801
|
|
|
802
|
+
// Export default declarations: export default class
|
|
803
|
+
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
|
|
804
|
+
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
805
|
+
const className = node.declaration.id.name;
|
|
806
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
807
|
+
|
|
808
|
+
classes.push({
|
|
809
|
+
name: className,
|
|
810
|
+
content: getCode(node.declaration),
|
|
811
|
+
methods: methods,
|
|
812
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
813
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
814
|
+
isExported: true,
|
|
815
|
+
isDefaultExport: true
|
|
816
|
+
});
|
|
817
|
+
// Mark as visited to avoid duplicate processing
|
|
818
|
+
visitedNodes.add(node.declaration);
|
|
819
|
+
} else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
820
|
+
functions.push({
|
|
821
|
+
name: node.declaration.id.name,
|
|
822
|
+
content: getCode(node.declaration),
|
|
823
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
824
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
825
|
+
isExported: true,
|
|
826
|
+
isDefaultExport: true
|
|
827
|
+
});
|
|
828
|
+
visitedNodes.add(node.declaration);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
745
832
|
// Recursively traverse children
|
|
746
833
|
for (const key in node) {
|
|
747
834
|
if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
|
|
@@ -820,10 +907,12 @@ function listAllFiles(dir, base = '', ignorePatterns = null) {
|
|
|
820
907
|
results.push(classFilePath);
|
|
821
908
|
|
|
822
909
|
// Add methods nested under the class: ClassName.methodName
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
910
|
+
if (cls.methods && cls.methods.length > 0) {
|
|
911
|
+
for (const method of cls.methods) {
|
|
912
|
+
const methodFileName = method.name + '.method';
|
|
913
|
+
const methodFilePath = path.join(classFilePath, methodFileName).replace(/\\\\/g, '/');
|
|
914
|
+
results.push(methodFilePath);
|
|
915
|
+
}
|
|
827
916
|
}
|
|
828
917
|
}
|
|
829
918
|
};
|
|
@@ -1296,405 +1385,6 @@ app.post('/prompt_agent', (req, res) => {
|
|
|
1296
1385
|
});
|
|
1297
1386
|
});
|
|
1298
1387
|
|
|
1299
|
-
// Helper function to resolve import path relative to source file
|
|
1300
|
-
function resolveImportPath(importPath, sourceFilePath) {
|
|
1301
|
-
// Remove root folder name if present
|
|
1302
|
-
let relativeSourcePath = sourceFilePath;
|
|
1303
|
-
const rootName = path.basename(ROOT);
|
|
1304
|
-
if (relativeSourcePath.startsWith(rootName + '/')) {
|
|
1305
|
-
relativeSourcePath = relativeSourcePath.substring(rootName.length + 1);
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
const sourceDir = path.dirname(path.join(ROOT, relativeSourcePath));
|
|
1309
|
-
|
|
1310
|
-
// Handle relative imports
|
|
1311
|
-
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
1312
|
-
const resolved = path.resolve(sourceDir, importPath);
|
|
1313
|
-
return path.relative(ROOT, resolved).replace(/\\\\/g, '/');
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// Handle absolute imports (node_modules, etc.)
|
|
1317
|
-
// For now, just return as-is - we'll check node_modules separately
|
|
1318
|
-
return importPath;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
// Helper function to check if a file exists (with various extensions)
|
|
1322
|
-
function findFileWithExtensions(basePath) {
|
|
1323
|
-
const extensions = ['', '.js', '.jsx', '.ts', '.tsx', '.py', '/index.js', '/index.ts', '/index.tsx'];
|
|
1324
|
-
const rootName = path.basename(ROOT);
|
|
1325
|
-
|
|
1326
|
-
for (const ext of extensions) {
|
|
1327
|
-
const testPath = basePath + ext;
|
|
1328
|
-
|
|
1329
|
-
// Remove root folder name if present
|
|
1330
|
-
let relativePath = testPath;
|
|
1331
|
-
if (relativePath.startsWith(rootName + '/')) {
|
|
1332
|
-
relativePath = relativePath.substring(rootName.length + 1);
|
|
1333
|
-
}
|
|
1334
|
-
const actualPath = path.join(ROOT, relativePath);
|
|
1335
|
-
|
|
1336
|
-
if (fs.existsSync(actualPath) && fs.statSync(actualPath).isFile()) {
|
|
1337
|
-
return relativePath;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
return null;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// Helper function to extract exports from JavaScript/TypeScript file
|
|
1345
|
-
function getJavaScriptExports(filePath) {
|
|
1346
|
-
try {
|
|
1347
|
-
if (!babelParser) {
|
|
1348
|
-
return { default: false, named: [], all: false };
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
1352
|
-
const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
|
|
1353
|
-
|
|
1354
|
-
const ast = babelParser.parse(content, {
|
|
1355
|
-
sourceType: 'module',
|
|
1356
|
-
plugins: [
|
|
1357
|
-
'typescript',
|
|
1358
|
-
'jsx',
|
|
1359
|
-
'decorators-legacy',
|
|
1360
|
-
'classProperties',
|
|
1361
|
-
'objectRestSpread',
|
|
1362
|
-
'asyncGenerators',
|
|
1363
|
-
'functionBind',
|
|
1364
|
-
'exportDefaultFrom',
|
|
1365
|
-
'exportNamespaceFrom',
|
|
1366
|
-
'dynamicImport',
|
|
1367
|
-
'nullishCoalescingOperator',
|
|
1368
|
-
'optionalChaining'
|
|
1369
|
-
]
|
|
1370
|
-
});
|
|
1371
|
-
|
|
1372
|
-
const exports = { default: false, named: [], all: false };
|
|
1373
|
-
|
|
1374
|
-
function traverse(node) {
|
|
1375
|
-
if (!node) return;
|
|
1376
|
-
|
|
1377
|
-
// export default
|
|
1378
|
-
if (node.type === 'ExportDefaultDeclaration') {
|
|
1379
|
-
exports.default = true;
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// export { A, B }
|
|
1383
|
-
if (node.type === 'ExportNamedDeclaration') {
|
|
1384
|
-
if (node.specifiers) {
|
|
1385
|
-
node.specifiers.forEach(spec => {
|
|
1386
|
-
if (spec.type === 'ExportSpecifier') {
|
|
1387
|
-
const name = spec.exported.name || spec.exported.value;
|
|
1388
|
-
if (name === '*') {
|
|
1389
|
-
exports.all = true;
|
|
1390
|
-
} else {
|
|
1391
|
-
exports.named.push(name);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
});
|
|
1395
|
-
}
|
|
1396
|
-
// export function/class
|
|
1397
|
-
if (node.declaration) {
|
|
1398
|
-
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
1399
|
-
exports.named.push(node.declaration.id.name);
|
|
1400
|
-
} else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
1401
|
-
exports.named.push(node.declaration.id.name);
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
// export * from './file'
|
|
1407
|
-
if (node.type === 'ExportAllDeclaration') {
|
|
1408
|
-
exports.all = true;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Recursively traverse
|
|
1412
|
-
for (const key in node) {
|
|
1413
|
-
if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
|
|
1414
|
-
const child = node[key];
|
|
1415
|
-
if (Array.isArray(child)) {
|
|
1416
|
-
child.forEach(c => traverse(c));
|
|
1417
|
-
} else if (child && typeof child === 'object' && child.type) {
|
|
1418
|
-
traverse(child);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
traverse(ast);
|
|
1424
|
-
return exports;
|
|
1425
|
-
} catch (e) {
|
|
1426
|
-
// If parsing fails, return empty exports
|
|
1427
|
-
return { default: false, named: [], all: false };
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// Helper function to check if symbol exists in Python file
|
|
1432
|
-
function checkPythonSymbol(filePath, symbolName) {
|
|
1433
|
-
try {
|
|
1434
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
1435
|
-
const lines = content.split('\\n');
|
|
1436
|
-
|
|
1437
|
-
// Check for function definitions
|
|
1438
|
-
const funcPattern = new RegExp(\`^\\\\s*def\\\\s+\${symbolName}\\\\s*\\\\(\`);
|
|
1439
|
-
// Check for class definitions
|
|
1440
|
-
const classPattern = new RegExp(\`^\\\\s*class\\\\s+\${symbolName}\\\\s*[(:]\`);
|
|
1441
|
-
// Check for variable assignments
|
|
1442
|
-
const varPattern = new RegExp(\`^\\\\s*\${symbolName}\\\\s*=\`);
|
|
1443
|
-
|
|
1444
|
-
for (const line of lines) {
|
|
1445
|
-
if (funcPattern.test(line) || classPattern.test(line) || varPattern.test(line)) {
|
|
1446
|
-
return true;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// Check __all__ if present
|
|
1451
|
-
const allMatch = content.match(/^__all__\\s*=\\s*\\[(.*?)\\]/m);
|
|
1452
|
-
if (allMatch) {
|
|
1453
|
-
const allExports = allMatch[1].split(',').map(s => s.trim().replace(/['"]/g, ''));
|
|
1454
|
-
return allExports.includes(symbolName);
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
return false;
|
|
1458
|
-
} catch (e) {
|
|
1459
|
-
return false;
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Validate imports for a single file
|
|
1464
|
-
app.post('/validate-imports', (req, res) => {
|
|
1465
|
-
try {
|
|
1466
|
-
const { filePath, imports } = req.body;
|
|
1467
|
-
|
|
1468
|
-
if (!filePath || !imports || !Array.isArray(imports)) {
|
|
1469
|
-
return res.status(400).json({ error: 'filePath and imports array required' });
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
const errors = [];
|
|
1473
|
-
const rootName = path.basename(ROOT);
|
|
1474
|
-
|
|
1475
|
-
// Remove root folder name if present
|
|
1476
|
-
let relativeFilePath = filePath;
|
|
1477
|
-
if (relativeFilePath.startsWith(rootName + '/')) {
|
|
1478
|
-
relativeFilePath = relativeFilePath.substring(rootName.length + 1);
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
for (const imp of imports) {
|
|
1482
|
-
const importPath = imp.path;
|
|
1483
|
-
const symbols = imp.symbols || [];
|
|
1484
|
-
|
|
1485
|
-
// Resolve import path
|
|
1486
|
-
const resolvedPath = resolveImportPath(importPath, filePath);
|
|
1487
|
-
|
|
1488
|
-
// Check if file exists
|
|
1489
|
-
const foundFile = findFileWithExtensions(resolvedPath);
|
|
1490
|
-
|
|
1491
|
-
if (!foundFile) {
|
|
1492
|
-
// Check if it's a node_modules import (skip validation for external packages)
|
|
1493
|
-
if (!importPath.startsWith('./') && !importPath.startsWith('../') && !importPath.startsWith('/')) {
|
|
1494
|
-
// Likely a node_modules import, skip
|
|
1495
|
-
continue;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
errors.push({
|
|
1499
|
-
line: imp.line,
|
|
1500
|
-
import: importPath,
|
|
1501
|
-
error: \`Cannot find module '\${importPath}'\`
|
|
1502
|
-
});
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Check exports for each symbol
|
|
1507
|
-
const fullFilePath = path.join(ROOT, foundFile);
|
|
1508
|
-
const extension = foundFile.split('.').pop()?.toLowerCase();
|
|
1509
|
-
|
|
1510
|
-
if (['js', 'jsx', 'ts', 'tsx'].includes(extension)) {
|
|
1511
|
-
const exports = getJavaScriptExports(fullFilePath);
|
|
1512
|
-
|
|
1513
|
-
for (const symbol of symbols) {
|
|
1514
|
-
if (symbol.isNamespace || symbol.name === '*') {
|
|
1515
|
-
// Namespace import - check if file has any exports
|
|
1516
|
-
if (!exports.default && exports.named.length === 0 && !exports.all) {
|
|
1517
|
-
errors.push({
|
|
1518
|
-
line: imp.line,
|
|
1519
|
-
import: importPath,
|
|
1520
|
-
symbol: symbol.name,
|
|
1521
|
-
error: \`Module '\${importPath}' has no exports\`
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
} else if (symbol.isDefault) {
|
|
1525
|
-
if (!exports.default) {
|
|
1526
|
-
errors.push({
|
|
1527
|
-
line: imp.line,
|
|
1528
|
-
import: importPath,
|
|
1529
|
-
symbol: symbol.name,
|
|
1530
|
-
error: \`'\${symbol.name}' is not exported as default from '\${importPath}'\`
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
} else {
|
|
1534
|
-
// Named export
|
|
1535
|
-
if (!exports.named.includes(symbol.name) && !exports.all) {
|
|
1536
|
-
errors.push({
|
|
1537
|
-
line: imp.line,
|
|
1538
|
-
import: importPath,
|
|
1539
|
-
symbol: symbol.name,
|
|
1540
|
-
error: \`'\${symbol.name}' is not exported from '\${importPath}'\`
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
} else if (extension === 'py') {
|
|
1546
|
-
// Python validation
|
|
1547
|
-
for (const symbol of symbols) {
|
|
1548
|
-
if (symbol.name === '*' || symbol.isNamespace) {
|
|
1549
|
-
// Wildcard import - just check if file exists (already done)
|
|
1550
|
-
continue;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// Check if symbol exists in Python file
|
|
1554
|
-
if (!checkPythonSymbol(fullFilePath, symbol.name)) {
|
|
1555
|
-
errors.push({
|
|
1556
|
-
line: imp.line,
|
|
1557
|
-
import: importPath,
|
|
1558
|
-
symbol: symbol.name,
|
|
1559
|
-
error: \`'\${symbol.name}' is not defined in '\${importPath}'\`
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
res.json({
|
|
1567
|
-
file: filePath,
|
|
1568
|
-
errors,
|
|
1569
|
-
errorCount: errors.length
|
|
1570
|
-
});
|
|
1571
|
-
} catch (e) {
|
|
1572
|
-
console.error('Error validating imports:', e);
|
|
1573
|
-
res.status(500).json({ error: e.message });
|
|
1574
|
-
}
|
|
1575
|
-
});
|
|
1576
|
-
|
|
1577
|
-
// Validate imports for multiple files (batch)
|
|
1578
|
-
app.post('/validate-imports-batch', (req, res) => {
|
|
1579
|
-
try {
|
|
1580
|
-
const { files } = req.body;
|
|
1581
|
-
|
|
1582
|
-
if (!files || !Array.isArray(files)) {
|
|
1583
|
-
return res.status(400).json({ error: 'files array required' });
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
const results = [];
|
|
1587
|
-
|
|
1588
|
-
for (const file of files) {
|
|
1589
|
-
const { filePath, imports } = file;
|
|
1590
|
-
|
|
1591
|
-
if (!filePath || !imports || !Array.isArray(imports)) {
|
|
1592
|
-
results.push({ file: filePath || 'unknown', errors: [], errorCount: 0 });
|
|
1593
|
-
continue;
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
const errors = [];
|
|
1597
|
-
const rootName = path.basename(ROOT);
|
|
1598
|
-
|
|
1599
|
-
// Remove root folder name if present
|
|
1600
|
-
let relativeFilePath = filePath;
|
|
1601
|
-
if (relativeFilePath.startsWith(rootName + '/')) {
|
|
1602
|
-
relativeFilePath = relativeFilePath.substring(rootName.length + 1);
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
for (const imp of imports) {
|
|
1606
|
-
const importPath = imp.path;
|
|
1607
|
-
const symbols = imp.symbols || [];
|
|
1608
|
-
|
|
1609
|
-
// Resolve import path
|
|
1610
|
-
const resolvedPath = resolveImportPath(importPath, filePath);
|
|
1611
|
-
|
|
1612
|
-
// Check if file exists
|
|
1613
|
-
const foundFile = findFileWithExtensions(resolvedPath);
|
|
1614
|
-
|
|
1615
|
-
if (!foundFile) {
|
|
1616
|
-
// Check if it's a node_modules import
|
|
1617
|
-
if (!importPath.startsWith('./') && !importPath.startsWith('../') && !importPath.startsWith('/')) {
|
|
1618
|
-
continue;
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
errors.push({
|
|
1622
|
-
line: imp.line,
|
|
1623
|
-
import: importPath,
|
|
1624
|
-
error: \`Cannot find module '\${importPath}'\`
|
|
1625
|
-
});
|
|
1626
|
-
continue;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
// Check exports
|
|
1630
|
-
const fullFilePath = path.join(ROOT, foundFile);
|
|
1631
|
-
const extension = foundFile.split('.').pop()?.toLowerCase();
|
|
1632
|
-
|
|
1633
|
-
if (['js', 'jsx', 'ts', 'tsx'].includes(extension)) {
|
|
1634
|
-
const exports = getJavaScriptExports(fullFilePath);
|
|
1635
|
-
|
|
1636
|
-
for (const symbol of symbols) {
|
|
1637
|
-
if (symbol.isNamespace || symbol.name === '*') {
|
|
1638
|
-
if (!exports.default && exports.named.length === 0 && !exports.all) {
|
|
1639
|
-
errors.push({
|
|
1640
|
-
line: imp.line,
|
|
1641
|
-
import: importPath,
|
|
1642
|
-
symbol: symbol.name,
|
|
1643
|
-
error: \`Module '\${importPath}' has no exports\`
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
} else if (symbol.isDefault) {
|
|
1647
|
-
if (!exports.default) {
|
|
1648
|
-
errors.push({
|
|
1649
|
-
line: imp.line,
|
|
1650
|
-
import: importPath,
|
|
1651
|
-
symbol: symbol.name,
|
|
1652
|
-
error: \`'\${symbol.name}' is not exported as default from '\${importPath}'\`
|
|
1653
|
-
});
|
|
1654
|
-
}
|
|
1655
|
-
} else {
|
|
1656
|
-
if (!exports.named.includes(symbol.name) && !exports.all) {
|
|
1657
|
-
errors.push({
|
|
1658
|
-
line: imp.line,
|
|
1659
|
-
import: importPath,
|
|
1660
|
-
symbol: symbol.name,
|
|
1661
|
-
error: \`'\${symbol.name}' is not exported from '\${importPath}'\`
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
} else if (extension === 'py') {
|
|
1667
|
-
for (const symbol of symbols) {
|
|
1668
|
-
if (symbol.name === '*' || symbol.isNamespace) {
|
|
1669
|
-
continue;
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
if (!checkPythonSymbol(fullFilePath, symbol.name)) {
|
|
1673
|
-
errors.push({
|
|
1674
|
-
line: imp.line,
|
|
1675
|
-
import: importPath,
|
|
1676
|
-
symbol: symbol.name,
|
|
1677
|
-
error: \`'\${symbol.name}' is not defined in '\${importPath}'\`
|
|
1678
|
-
});
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
results.push({
|
|
1685
|
-
file: filePath,
|
|
1686
|
-
errors,
|
|
1687
|
-
errorCount: errors.length
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
res.json(results);
|
|
1692
|
-
} catch (e) {
|
|
1693
|
-
console.error('Error validating imports batch:', e);
|
|
1694
|
-
res.status(500).json({ error: e.message });
|
|
1695
|
-
}
|
|
1696
|
-
});
|
|
1697
|
-
|
|
1698
1388
|
// Shutdown endpoint to kill the server
|
|
1699
1389
|
app.post('/shutdown', (req, res) => {
|
|
1700
1390
|
console.log('🛑 Shutdown endpoint called - terminating server...');
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devbonzai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
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": {
|
|
7
|
-
"
|
|
7
|
+
"devchart": "./cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -21,7 +21,6 @@
|
|
|
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"
|
|
25
|
-
"@babel/parser": "^7.23.0"
|
|
24
|
+
"raw-body": "^2.5.2"
|
|
26
25
|
}
|
|
27
26
|
}
|