devbonzai 1.8.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -69
- package/cli.js +128 -466
- 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,28 @@ 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
|
-
for (const member of node.body.body) {
|
|
460
|
+
if (classNode.body && classNode.body.body) {
|
|
461
|
+
for (const member of classNode.body.body) {
|
|
462
|
+
// Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
|
|
468
463
|
if (member.type === 'MethodDefinition' && member.key) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
+
|
|
472
479
|
methods.push({
|
|
473
480
|
name: className + '.' + methodName,
|
|
474
481
|
content: getCode(member),
|
|
@@ -476,11 +483,22 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
476
483
|
endLine: member.loc ? member.loc.end.line : 0,
|
|
477
484
|
isMethod: true,
|
|
478
485
|
className: className,
|
|
479
|
-
methodName: methodName
|
|
486
|
+
methodName: methodName,
|
|
487
|
+
kind: kind,
|
|
488
|
+
static: isStatic
|
|
480
489
|
});
|
|
481
490
|
}
|
|
482
491
|
}
|
|
483
492
|
}
|
|
493
|
+
return methods;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Class declarations: class User { ... }
|
|
497
|
+
// Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
|
|
498
|
+
if (node.type === 'ClassDeclaration' && node.id &&
|
|
499
|
+
parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
|
|
500
|
+
const className = node.id.name;
|
|
501
|
+
const methods = extractClassMethods(node, className);
|
|
484
502
|
|
|
485
503
|
classes.push({
|
|
486
504
|
name: className,
|
|
@@ -491,7 +509,7 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
491
509
|
});
|
|
492
510
|
}
|
|
493
511
|
|
|
494
|
-
// Export declarations: export function, export
|
|
512
|
+
// Export declarations: export function, export class
|
|
495
513
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
496
514
|
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
497
515
|
functions.push({
|
|
@@ -505,26 +523,7 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
505
523
|
visitedNodes.add(node.declaration);
|
|
506
524
|
} else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
507
525
|
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
|
-
}
|
|
526
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
528
527
|
|
|
529
528
|
classes.push({
|
|
530
529
|
name: className,
|
|
@@ -539,6 +538,36 @@ function extractJavaScriptFunctions(filePath) {
|
|
|
539
538
|
}
|
|
540
539
|
}
|
|
541
540
|
|
|
541
|
+
// Export default declarations: export default class
|
|
542
|
+
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
|
|
543
|
+
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
544
|
+
const className = node.declaration.id.name;
|
|
545
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
546
|
+
|
|
547
|
+
classes.push({
|
|
548
|
+
name: className,
|
|
549
|
+
content: getCode(node.declaration),
|
|
550
|
+
methods: methods,
|
|
551
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
552
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
553
|
+
isExported: true,
|
|
554
|
+
isDefaultExport: true
|
|
555
|
+
});
|
|
556
|
+
// Mark as visited to avoid duplicate processing
|
|
557
|
+
visitedNodes.add(node.declaration);
|
|
558
|
+
} else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
559
|
+
functions.push({
|
|
560
|
+
name: node.declaration.id.name,
|
|
561
|
+
content: getCode(node.declaration),
|
|
562
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
563
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
564
|
+
isExported: true,
|
|
565
|
+
isDefaultExport: true
|
|
566
|
+
});
|
|
567
|
+
visitedNodes.add(node.declaration);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
542
571
|
// Recursively traverse children
|
|
543
572
|
for (const key in node) {
|
|
544
573
|
if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
|
|
@@ -660,18 +689,28 @@ function extractVueFunctions(filePath) {
|
|
|
660
689
|
});
|
|
661
690
|
}
|
|
662
691
|
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
if (node.type === 'ClassDeclaration' && node.id && parentType !== 'ExportNamedDeclaration') {
|
|
666
|
-
const className = node.id.name;
|
|
692
|
+
// Helper function to extract methods from a class body
|
|
693
|
+
const extractClassMethods = (classNode, className) => {
|
|
667
694
|
const methods = [];
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
695
|
+
if (classNode.body && classNode.body.body) {
|
|
696
|
+
for (const member of classNode.body.body) {
|
|
697
|
+
// Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
|
|
671
698
|
if (member.type === 'MethodDefinition' && member.key) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
699
|
+
let methodName;
|
|
700
|
+
if (member.key.type === 'Identifier') {
|
|
701
|
+
methodName = member.key.name;
|
|
702
|
+
} else if (member.key.type === 'PrivateName') {
|
|
703
|
+
methodName = '#' + member.key.id.name;
|
|
704
|
+
} else if (member.key.type === 'StringLiteral' || member.key.type === 'NumericLiteral') {
|
|
705
|
+
methodName = String(member.key.value);
|
|
706
|
+
} else {
|
|
707
|
+
methodName = String(member.key.value || member.key.name || 'unknown');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Include kind (constructor, get, set, method) in the name for clarity
|
|
711
|
+
const kind = member.kind || 'method';
|
|
712
|
+
const isStatic = member.static || false;
|
|
713
|
+
|
|
675
714
|
methods.push({
|
|
676
715
|
name: className + '.' + methodName,
|
|
677
716
|
content: getCode(member),
|
|
@@ -679,11 +718,22 @@ function extractVueFunctions(filePath) {
|
|
|
679
718
|
endLine: member.loc ? member.loc.end.line : 0,
|
|
680
719
|
isMethod: true,
|
|
681
720
|
className: className,
|
|
682
|
-
methodName: methodName
|
|
721
|
+
methodName: methodName,
|
|
722
|
+
kind: kind,
|
|
723
|
+
static: isStatic
|
|
683
724
|
});
|
|
684
725
|
}
|
|
685
726
|
}
|
|
686
727
|
}
|
|
728
|
+
return methods;
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// Class declarations: class User { ... }
|
|
732
|
+
// Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
|
|
733
|
+
if (node.type === 'ClassDeclaration' && node.id &&
|
|
734
|
+
parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
|
|
735
|
+
const className = node.id.name;
|
|
736
|
+
const methods = extractClassMethods(node, className);
|
|
687
737
|
|
|
688
738
|
classes.push({
|
|
689
739
|
name: className,
|
|
@@ -694,7 +744,7 @@ function extractVueFunctions(filePath) {
|
|
|
694
744
|
});
|
|
695
745
|
}
|
|
696
746
|
|
|
697
|
-
// Export declarations: export function, export
|
|
747
|
+
// Export declarations: export function, export class
|
|
698
748
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
699
749
|
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
700
750
|
functions.push({
|
|
@@ -708,26 +758,7 @@ function extractVueFunctions(filePath) {
|
|
|
708
758
|
visitedNodes.add(node.declaration);
|
|
709
759
|
} else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
710
760
|
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
|
-
}
|
|
761
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
731
762
|
|
|
732
763
|
classes.push({
|
|
733
764
|
name: className,
|
|
@@ -742,6 +773,36 @@ function extractVueFunctions(filePath) {
|
|
|
742
773
|
}
|
|
743
774
|
}
|
|
744
775
|
|
|
776
|
+
// Export default declarations: export default class
|
|
777
|
+
if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
|
|
778
|
+
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
|
779
|
+
const className = node.declaration.id.name;
|
|
780
|
+
const methods = extractClassMethods(node.declaration, className);
|
|
781
|
+
|
|
782
|
+
classes.push({
|
|
783
|
+
name: className,
|
|
784
|
+
content: getCode(node.declaration),
|
|
785
|
+
methods: methods,
|
|
786
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
787
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
788
|
+
isExported: true,
|
|
789
|
+
isDefaultExport: true
|
|
790
|
+
});
|
|
791
|
+
// Mark as visited to avoid duplicate processing
|
|
792
|
+
visitedNodes.add(node.declaration);
|
|
793
|
+
} else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
794
|
+
functions.push({
|
|
795
|
+
name: node.declaration.id.name,
|
|
796
|
+
content: getCode(node.declaration),
|
|
797
|
+
startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
|
|
798
|
+
endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
|
|
799
|
+
isExported: true,
|
|
800
|
+
isDefaultExport: true
|
|
801
|
+
});
|
|
802
|
+
visitedNodes.add(node.declaration);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
745
806
|
// Recursively traverse children
|
|
746
807
|
for (const key in node) {
|
|
747
808
|
if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
|
|
@@ -1296,405 +1357,6 @@ app.post('/prompt_agent', (req, res) => {
|
|
|
1296
1357
|
});
|
|
1297
1358
|
});
|
|
1298
1359
|
|
|
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
1360
|
// Shutdown endpoint to kill the server
|
|
1699
1361
|
app.post('/shutdown', (req, res) => {
|
|
1700
1362
|
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.1",
|
|
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
|
}
|