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.
- package/cli.js +624 -35
- 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
|
|
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
|
|
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
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
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 =
|
|
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
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
//
|
|
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(
|
|
314
|
-
return res.status(404).send('Parent
|
|
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
|
-
//
|
|
318
|
-
const
|
|
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 (!
|
|
322
|
-
return res.status(404).send('
|
|
907
|
+
if (!content) {
|
|
908
|
+
return res.status(404).send(\`\${itemType} '\${itemName}' not found in file\`);
|
|
323
909
|
}
|
|
324
910
|
|
|
325
|
-
return res.json({ content
|
|
911
|
+
return res.json({ content });
|
|
326
912
|
} catch (e) {
|
|
327
|
-
|
|
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.
|
|
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
|
}
|