devbonzai 1.7.9 → 1.8.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 +69 -1
- package/cli.js +455 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,4 +13,72 @@ 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.
|
|
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
|
package/cli.js
CHANGED
|
@@ -92,6 +92,8 @@ 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})',
|
|
95
97
|
'POST /shutdown': 'Gracefully shutdown the server'
|
|
96
98
|
},
|
|
97
99
|
example: 'Try: /list or /read?path=README.md'
|
|
@@ -817,10 +819,10 @@ function listAllFiles(dir, base = '', ignorePatterns = null) {
|
|
|
817
819
|
const classFilePath = path.join(filePath, className).replace(/\\\\/g, '/');
|
|
818
820
|
results.push(classFilePath);
|
|
819
821
|
|
|
820
|
-
// Add methods
|
|
822
|
+
// Add methods nested under the class: ClassName.methodName
|
|
821
823
|
for (const method of cls.methods) {
|
|
822
824
|
const methodFileName = method.name + '.method';
|
|
823
|
-
const methodFilePath = path.join(
|
|
825
|
+
const methodFilePath = path.join(classFilePath, methodFileName).replace(/\\\\/g, '/');
|
|
824
826
|
results.push(methodFilePath);
|
|
825
827
|
}
|
|
826
828
|
}
|
|
@@ -889,47 +891,66 @@ app.get('/read', (req, res) => {
|
|
|
889
891
|
|
|
890
892
|
// Check if this is a virtual file request (.function, .method, or .class)
|
|
891
893
|
if (requestedPath.endsWith('.function') || requestedPath.endsWith('.method') || requestedPath.endsWith('.class')) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
// Determine file type and parser
|
|
897
|
-
let parseResult = null;
|
|
894
|
+
// Traverse up the path to find the actual source file
|
|
895
|
+
let currentPath = filePath;
|
|
896
|
+
let sourceFilePath = null;
|
|
898
897
|
let parser = null;
|
|
899
898
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
899
|
+
// Keep going up until we find a source file (.py, .js, .jsx, .ts, .tsx, .vue)
|
|
900
|
+
while (currentPath !== ROOT && currentPath !== path.dirname(currentPath)) {
|
|
901
|
+
const stat = fs.existsSync(currentPath) ? fs.statSync(currentPath) : null;
|
|
902
|
+
|
|
903
|
+
// Check if current path is a file with a supported extension
|
|
904
|
+
if (stat && stat.isFile()) {
|
|
905
|
+
if (currentPath.endsWith('.py')) {
|
|
906
|
+
parser = extractPythonFunctions;
|
|
907
|
+
sourceFilePath = currentPath;
|
|
908
|
+
break;
|
|
909
|
+
} else if (currentPath.endsWith('.js') || currentPath.endsWith('.jsx') ||
|
|
910
|
+
currentPath.endsWith('.ts') || currentPath.endsWith('.tsx')) {
|
|
911
|
+
parser = extractJavaScriptFunctions;
|
|
912
|
+
sourceFilePath = currentPath;
|
|
913
|
+
break;
|
|
914
|
+
} else if (currentPath.endsWith('.vue')) {
|
|
915
|
+
parser = extractVueFunctions;
|
|
916
|
+
sourceFilePath = currentPath;
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Move up one level
|
|
922
|
+
const parentPath = path.dirname(currentPath);
|
|
923
|
+
if (parentPath === currentPath) break; // Reached root
|
|
924
|
+
currentPath = parentPath;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (!sourceFilePath || !parser) {
|
|
928
|
+
return res.status(404).send('Source file not found for virtual file');
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Extract the requested item name from the requested path
|
|
932
|
+
let itemName = '';
|
|
933
|
+
let itemType = '';
|
|
934
|
+
|
|
935
|
+
if (requestedPath.endsWith('.function')) {
|
|
936
|
+
itemName = path.basename(requestedPath, '.function');
|
|
937
|
+
itemType = 'function';
|
|
938
|
+
} else if (requestedPath.endsWith('.method')) {
|
|
939
|
+
itemName = path.basename(requestedPath, '.method');
|
|
940
|
+
itemType = 'method';
|
|
941
|
+
} else if (requestedPath.endsWith('.class')) {
|
|
942
|
+
itemName = path.basename(requestedPath, '.class');
|
|
943
|
+
itemType = 'class';
|
|
908
944
|
}
|
|
909
945
|
|
|
910
|
-
// Check if the
|
|
946
|
+
// Check if the source file exists
|
|
911
947
|
try {
|
|
912
|
-
if (!fs.existsSync(
|
|
913
|
-
return res.status(404).send('
|
|
948
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
949
|
+
return res.status(404).send('Source file not found');
|
|
914
950
|
}
|
|
915
951
|
|
|
916
952
|
// Parse the file
|
|
917
|
-
parseResult = parser(
|
|
918
|
-
|
|
919
|
-
// Extract the requested item name
|
|
920
|
-
let itemName = '';
|
|
921
|
-
let itemType = '';
|
|
922
|
-
|
|
923
|
-
if (requestedPath.endsWith('.function')) {
|
|
924
|
-
itemName = path.basename(requestedPath, '.function');
|
|
925
|
-
itemType = 'function';
|
|
926
|
-
} else if (requestedPath.endsWith('.method')) {
|
|
927
|
-
itemName = path.basename(requestedPath, '.method');
|
|
928
|
-
itemType = 'method';
|
|
929
|
-
} else if (requestedPath.endsWith('.class')) {
|
|
930
|
-
itemName = path.basename(requestedPath, '.class');
|
|
931
|
-
itemType = 'class';
|
|
932
|
-
}
|
|
953
|
+
const parseResult = parser(sourceFilePath);
|
|
933
954
|
|
|
934
955
|
// Find and return the content
|
|
935
956
|
const content = findAndReturn(parseResult, itemName, itemType);
|
|
@@ -1275,6 +1296,405 @@ app.post('/prompt_agent', (req, res) => {
|
|
|
1275
1296
|
});
|
|
1276
1297
|
});
|
|
1277
1298
|
|
|
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
|
+
|
|
1278
1698
|
// Shutdown endpoint to kill the server
|
|
1279
1699
|
app.post('/shutdown', (req, res) => {
|
|
1280
1700
|
console.log('🛑 Shutdown endpoint called - terminating server...');
|