abapgit-agent 1.1.6 → 1.3.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/.github/workflows/release.yml +3 -1
- package/API.md +261 -0
- package/CLAUDE.md +384 -0
- package/README.md +16 -2
- package/RELEASE_NOTES.md +93 -8
- package/abap/CLAUDE.md +282 -6
- package/abap/copilot-instructions.md +79 -0
- package/abap/zcl_abgagt_agent.clas.abap +2 -2
- package/abap/zcl_abgagt_cmd_factory.clas.abap +2 -0
- package/abap/zcl_abgagt_command_inspect.clas.abap +255 -36
- package/abap/zcl_abgagt_command_tree.clas.abap +237 -0
- package/abap/zcl_abgagt_command_tree.clas.xml +15 -0
- package/abap/zcl_abgagt_command_view.clas.abap +240 -0
- package/abap/zcl_abgagt_command_view.clas.xml +15 -0
- package/abap/zcl_abgagt_resource_tree.clas.abap +70 -0
- package/abap/zcl_abgagt_resource_tree.clas.xml +15 -0
- package/abap/zcl_abgagt_resource_view.clas.abap +68 -0
- package/abap/zcl_abgagt_resource_view.clas.xml +15 -0
- package/abap/zcl_abgagt_rest_handler.clas.abap +2 -0
- package/abap/zcl_abgagt_util.clas.abap +2 -2
- package/abap/zcl_abgagt_viewer_clas.clas.abap +58 -0
- package/abap/zcl_abgagt_viewer_clas.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_ddls.clas.abap +83 -0
- package/abap/zcl_abgagt_viewer_ddls.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_dtel.clas.abap +98 -0
- package/abap/zcl_abgagt_viewer_dtel.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_factory.clas.abap +41 -0
- package/abap/zcl_abgagt_viewer_factory.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_intf.clas.abap +58 -0
- package/abap/zcl_abgagt_viewer_intf.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_stru.clas.abap +59 -0
- package/abap/zcl_abgagt_viewer_stru.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_tabl.clas.abap +59 -0
- package/abap/zcl_abgagt_viewer_tabl.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_ttyp.clas.abap +93 -0
- package/abap/zcl_abgagt_viewer_ttyp.clas.xml +15 -0
- package/abap/zif_abgagt_command.intf.abap +3 -1
- package/abap/zif_abgagt_viewer.intf.abap +12 -0
- package/abap/zif_abgagt_viewer.intf.xml +15 -0
- package/bin/abapgit-agent +605 -38
- package/docs/commands.md +27 -8
- package/docs/tree-command.md +303 -0
- package/docs/view-command.md +501 -0
- package/package.json +1 -1
- package/src/abap-client.js +22 -0
- package/src/agent.js +27 -0
package/bin/abapgit-agent
CHANGED
|
@@ -309,33 +309,70 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
|
|
|
309
309
|
|
|
310
310
|
const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
|
|
311
311
|
|
|
312
|
-
// Handle
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
// Handle new table-based result format (array) or old single result
|
|
313
|
+
let results = [];
|
|
314
|
+
if (Array.isArray(result)) {
|
|
315
|
+
results = result;
|
|
316
|
+
} else {
|
|
317
|
+
// Convert old single result to array format for compatibility
|
|
318
|
+
results = [{
|
|
319
|
+
OBJECT_TYPE: 'UNKNOWN',
|
|
320
|
+
OBJECT_NAME: pathModule.basename(sourceFile),
|
|
321
|
+
SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
|
|
322
|
+
ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
|
|
323
|
+
ERRORS: result.ERRORS || result.errors || [],
|
|
324
|
+
WARNINGS: result.warnings || []
|
|
325
|
+
}];
|
|
326
|
+
}
|
|
316
327
|
|
|
317
|
-
|
|
328
|
+
// Process each result
|
|
329
|
+
let hasErrors = false;
|
|
330
|
+
for (const res of results) {
|
|
331
|
+
// Handle both uppercase and lowercase keys
|
|
332
|
+
const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
|
|
333
|
+
const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
|
|
334
|
+
const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
|
|
335
|
+
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
336
|
+
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
337
|
+
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
338
|
+
|
|
339
|
+
console.log('\n');
|
|
340
|
+
|
|
341
|
+
if (errorCount > 0 || warnings.length > 0) {
|
|
342
|
+
if (errorCount > 0) {
|
|
343
|
+
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
|
|
346
|
+
}
|
|
347
|
+
console.log('\nErrors:');
|
|
348
|
+
console.log('─'.repeat(60));
|
|
318
349
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
350
|
+
for (const err of errors) {
|
|
351
|
+
const line = err.LINE || err.line || '?';
|
|
352
|
+
const column = err.COLUMN || err.column || '?';
|
|
353
|
+
const text = err.TEXT || err.text || 'Unknown error';
|
|
323
354
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
355
|
+
console.log(` Line ${line}, Column ${column}:`);
|
|
356
|
+
console.log(` ${text}`);
|
|
357
|
+
console.log('');
|
|
358
|
+
}
|
|
328
359
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
360
|
+
// Show warnings if any
|
|
361
|
+
if (warnings.length > 0) {
|
|
362
|
+
console.log('Warnings:');
|
|
363
|
+
console.log('─'.repeat(60));
|
|
364
|
+
for (const warn of warnings) {
|
|
365
|
+
const line = warn.LINE || warn.line || '?';
|
|
366
|
+
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
367
|
+
console.log(` Line ${line}: ${text}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
hasErrors = true;
|
|
371
|
+
} else if (success === true || success === 'X') {
|
|
372
|
+
console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
|
|
373
|
+
} else {
|
|
374
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
|
|
332
375
|
}
|
|
333
|
-
} else if (success === 'X' || success === true) {
|
|
334
|
-
console.log(`✅ ${pathModule.basename(sourceFile)} - Syntax check passed (0 errors)`);
|
|
335
|
-
} else if (success === false) {
|
|
336
|
-
console.log(`❌ ${pathModule.basename(sourceFile)} - Syntax check failed`);
|
|
337
|
-
} else {
|
|
338
|
-
console.log(`⚠️ Syntax check returned unexpected status: ${success}`);
|
|
339
376
|
}
|
|
340
377
|
|
|
341
378
|
return result;
|
|
@@ -345,6 +382,95 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
|
|
|
345
382
|
}
|
|
346
383
|
}
|
|
347
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Inspect all files in one request
|
|
387
|
+
*/
|
|
388
|
+
async function inspectAllFiles(files, csrfToken, config) {
|
|
389
|
+
// Convert files to uppercase names (same as syntaxCheckSource does)
|
|
390
|
+
const fileNames = files.map(f => {
|
|
391
|
+
const baseName = pathModule.basename(f).toUpperCase();
|
|
392
|
+
return baseName;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
// Send all files in one request
|
|
397
|
+
const data = {
|
|
398
|
+
files: fileNames
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
|
|
402
|
+
|
|
403
|
+
// Handle both table result and old single result
|
|
404
|
+
let results = [];
|
|
405
|
+
if (Array.isArray(result)) {
|
|
406
|
+
results = result;
|
|
407
|
+
} else {
|
|
408
|
+
// Convert single result to array format
|
|
409
|
+
results = [{
|
|
410
|
+
OBJECT_TYPE: 'UNKNOWN',
|
|
411
|
+
OBJECT_NAME: files.join(', '),
|
|
412
|
+
SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
|
|
413
|
+
ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
|
|
414
|
+
ERRORS: result.ERRORS || result.errors || [],
|
|
415
|
+
WARNINGS: result.warnings || []
|
|
416
|
+
}];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return results;
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.error(`\n Error: ${error.message}`);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Process a single inspect result
|
|
428
|
+
*/
|
|
429
|
+
async function processInspectResult(res) {
|
|
430
|
+
// Handle both uppercase and lowercase keys
|
|
431
|
+
const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
|
|
432
|
+
const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
|
|
433
|
+
const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
|
|
434
|
+
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
435
|
+
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
436
|
+
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
437
|
+
|
|
438
|
+
if (errorCount > 0 || warnings.length > 0) {
|
|
439
|
+
if (errorCount > 0) {
|
|
440
|
+
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
441
|
+
} else {
|
|
442
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
|
|
443
|
+
}
|
|
444
|
+
console.log('\nErrors:');
|
|
445
|
+
console.log('─'.repeat(60));
|
|
446
|
+
|
|
447
|
+
for (const err of errors) {
|
|
448
|
+
const line = err.LINE || err.line || '?';
|
|
449
|
+
const column = err.COLUMN || err.column || '?';
|
|
450
|
+
const text = err.TEXT || err.text || 'Unknown error';
|
|
451
|
+
|
|
452
|
+
console.log(` Line ${line}, Column ${column}:`);
|
|
453
|
+
console.log(` ${text}`);
|
|
454
|
+
console.log('');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Show warnings if any
|
|
458
|
+
if (warnings.length > 0) {
|
|
459
|
+
console.log('Warnings:');
|
|
460
|
+
console.log('─'.repeat(60));
|
|
461
|
+
for (const warn of warnings) {
|
|
462
|
+
const line = warn.LINE || warn.line || '?';
|
|
463
|
+
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
464
|
+
console.log(` Line ${line}: ${text}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else if (success === true || success === 'X') {
|
|
468
|
+
console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
|
|
469
|
+
} else {
|
|
470
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
348
474
|
/**
|
|
349
475
|
* Run unit tests for package or objects
|
|
350
476
|
*/
|
|
@@ -495,6 +621,136 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
|
|
|
495
621
|
}
|
|
496
622
|
}
|
|
497
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Run tree command and return raw result
|
|
626
|
+
*/
|
|
627
|
+
async function runTreeCommand(packageName, depth, includeObjects, csrfToken, config) {
|
|
628
|
+
const data = {
|
|
629
|
+
package: packageName,
|
|
630
|
+
depth: depth,
|
|
631
|
+
include_objects: includeObjects
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
return await request('POST', '/sap/bc/z_abapgit_agent/tree', data, { csrfToken });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Display tree output in human-readable format
|
|
639
|
+
*/
|
|
640
|
+
async function displayTreeOutput(packageName, depth, includeObjects) {
|
|
641
|
+
const config = loadConfig();
|
|
642
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
643
|
+
|
|
644
|
+
console.log(`\n Getting package tree for: ${packageName}`);
|
|
645
|
+
|
|
646
|
+
const result = await runTreeCommand(packageName, depth, includeObjects, csrfToken, config);
|
|
647
|
+
|
|
648
|
+
// Handle uppercase keys from ABAP
|
|
649
|
+
const success = result.SUCCESS || result.success;
|
|
650
|
+
const error = result.ERROR || result.error;
|
|
651
|
+
|
|
652
|
+
if (!success || error) {
|
|
653
|
+
console.error(`\n ❌ Error: ${error || 'Failed to get tree'}`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Parse hierarchy structure (ABAP returns flat nodes with parent refs)
|
|
658
|
+
const nodes = result.NODES || result.nodes || [];
|
|
659
|
+
const rootPackage = result.PACKAGE || result.package || packageName;
|
|
660
|
+
const parentPackage = result.PARENT_PACKAGE || result.parent_package;
|
|
661
|
+
const totalPackages = result.TOTAL_PACKAGES || result.total_packages || 0;
|
|
662
|
+
const totalObjects = result.TOTAL_OBJECTS || result.total_objects || 0;
|
|
663
|
+
const objectTypes = result.OBJECTS || result.objects || [];
|
|
664
|
+
|
|
665
|
+
console.log(`\n Package Tree: ${rootPackage}`);
|
|
666
|
+
|
|
667
|
+
// Display parent info if available
|
|
668
|
+
if (parentPackage && parentPackage !== rootPackage) {
|
|
669
|
+
console.log(` ⬆️ Parent: ${parentPackage}`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
console.log('');
|
|
673
|
+
|
|
674
|
+
// Build and display tree from flat nodes list
|
|
675
|
+
const lines = buildTreeLinesFromNodes(nodes, '', true);
|
|
676
|
+
for (const line of lines) {
|
|
677
|
+
console.log(` ${line}`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
console.log('');
|
|
681
|
+
console.log(' Summary');
|
|
682
|
+
console.log(` PACKAGES: ${totalPackages}`);
|
|
683
|
+
console.log(` OBJECTS: ${totalObjects}`);
|
|
684
|
+
|
|
685
|
+
// Display object types if available
|
|
686
|
+
if (includeObjects && objectTypes.length > 0) {
|
|
687
|
+
const typeStr = objectTypes.map(t => `${t.OBJECT || t.object}=${t.COUNT || t.count}`).join(' ');
|
|
688
|
+
console.log(` TYPES: ${typeStr}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Build tree display lines from flat nodes list
|
|
694
|
+
*/
|
|
695
|
+
function buildTreeLinesFromNodes(nodes, prefix, isLast) {
|
|
696
|
+
const lines = [];
|
|
697
|
+
|
|
698
|
+
if (nodes.length === 0) {
|
|
699
|
+
return lines;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// First node is the root
|
|
703
|
+
const root = nodes[0];
|
|
704
|
+
const icon = '📦';
|
|
705
|
+
lines.push(`${prefix}${isLast ? '└─ ' : '├─ '} ${icon} ${root.PACKAGE || root.package}`);
|
|
706
|
+
|
|
707
|
+
// Get children (nodes with depth > 0, grouped by depth)
|
|
708
|
+
const children = nodes.filter(n => (n.DEPTH || n.depth) > 0);
|
|
709
|
+
|
|
710
|
+
// Group children by depth
|
|
711
|
+
const byDepth = {};
|
|
712
|
+
children.forEach(n => {
|
|
713
|
+
const d = n.DEPTH || n.depth;
|
|
714
|
+
if (!byDepth[d]) byDepth[d] = [];
|
|
715
|
+
byDepth[d].push(n);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Process depth 1 children
|
|
719
|
+
const depth1 = byDepth[1] || [];
|
|
720
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
721
|
+
|
|
722
|
+
for (let i = 0; i < depth1.length; i++) {
|
|
723
|
+
const child = depth1[i];
|
|
724
|
+
const isSubLast = i === depth1.length - 1;
|
|
725
|
+
const childLines = buildChildLines(child, newPrefix, isSubLast, byDepth);
|
|
726
|
+
lines.push(...childLines);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return lines;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function buildChildLines(node, prefix, isLast, byDepth) {
|
|
733
|
+
const lines = [];
|
|
734
|
+
const icon = '📦';
|
|
735
|
+
lines.push(`${prefix}${isLast ? '└─ ' : '├─ '} ${icon} ${node.PACKAGE || node.package}`);
|
|
736
|
+
|
|
737
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
738
|
+
const nodeDepth = node.DEPTH || node.depth;
|
|
739
|
+
const children = byDepth[nodeDepth + 1] || [];
|
|
740
|
+
|
|
741
|
+
// Find children of this node
|
|
742
|
+
const myChildren = children.filter(n => (n.PARENT || n.parent) === (node.PACKAGE || node.package));
|
|
743
|
+
|
|
744
|
+
for (let i = 0; i < myChildren.length; i++) {
|
|
745
|
+
const child = myChildren[i];
|
|
746
|
+
const isSubLast = i === myChildren.length - 1;
|
|
747
|
+
const childLines = buildChildLines(child, newPrefix, isSubLast, byDepth);
|
|
748
|
+
lines.push(...childLines);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return lines;
|
|
752
|
+
}
|
|
753
|
+
|
|
498
754
|
/**
|
|
499
755
|
* Pull and activate repository
|
|
500
756
|
*/
|
|
@@ -561,7 +817,46 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
|
|
|
561
817
|
'W': '⚠️', // Warning
|
|
562
818
|
'A': '🛑' // Abort
|
|
563
819
|
};
|
|
564
|
-
return icons[type] || '
|
|
820
|
+
return icons[type] || '';
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
|
|
824
|
+
const calcWidth = (str) => {
|
|
825
|
+
if (!str) return 0;
|
|
826
|
+
let width = 0;
|
|
827
|
+
let i = 0;
|
|
828
|
+
while (i < str.length) {
|
|
829
|
+
const code = str.codePointAt(i);
|
|
830
|
+
if (!code) break;
|
|
831
|
+
// Variation selectors (FE00-FE0F) and ZWJ (200D) take 0 width
|
|
832
|
+
if (code >= 0xFE00 && code <= 0xFE0F) {
|
|
833
|
+
i += 1;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (code === 0x200D) { // Zero width joiner
|
|
837
|
+
i += 1;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
// Emoji and wide characters take 2 cells
|
|
841
|
+
if (code > 0xFFFF) {
|
|
842
|
+
width += 2;
|
|
843
|
+
i += 2; // Skip surrogate pair
|
|
844
|
+
} else if (code > 127) {
|
|
845
|
+
width += 2;
|
|
846
|
+
i += 1;
|
|
847
|
+
} else {
|
|
848
|
+
width += 1;
|
|
849
|
+
i += 1;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return width;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// Pad string to display width
|
|
856
|
+
const padToWidth = (str, width) => {
|
|
857
|
+
const s = str || '';
|
|
858
|
+
const currentWidth = calcWidth(s);
|
|
859
|
+
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
565
860
|
};
|
|
566
861
|
|
|
567
862
|
if (success === 'X' || success === true) {
|
|
@@ -589,23 +884,15 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
|
|
|
589
884
|
|
|
590
885
|
// Calculate column widths based on terminal width
|
|
591
886
|
const tableWidth = Math.min(TERM_WIDTH, 120);
|
|
592
|
-
const iconCol =
|
|
887
|
+
const iconCol = 4; // Fixed width for icon column
|
|
593
888
|
const objCol = 28;
|
|
594
889
|
const msgCol = tableWidth - iconCol - objCol - 6; // Account for vertical lines (3 chars)
|
|
595
890
|
|
|
596
|
-
//
|
|
597
|
-
const getVisibleWidth = (str) => {
|
|
598
|
-
let width = 0;
|
|
599
|
-
for (const char of str) {
|
|
600
|
-
width += (char.charCodeAt(0) > 127) ? 2 : 1; // Emoji/wide chars = 2
|
|
601
|
-
}
|
|
602
|
-
return width;
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
// Helper to pad to visible width
|
|
891
|
+
// Pad string to display width using calcWidth for emoji support
|
|
606
892
|
const padToWidth = (str, width) => {
|
|
607
|
-
const
|
|
608
|
-
|
|
893
|
+
const s = str || '';
|
|
894
|
+
const currentWidth = calcWidth(s);
|
|
895
|
+
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
609
896
|
};
|
|
610
897
|
|
|
611
898
|
const headerLine = '─'.repeat(iconCol) + '┼' + '─'.repeat(objCol) + '┼' + '─'.repeat(msgCol);
|
|
@@ -818,6 +1105,25 @@ async function runInit(args) {
|
|
|
818
1105
|
console.error(`Error copying CLAUDE.md: ${error.message}`);
|
|
819
1106
|
}
|
|
820
1107
|
|
|
1108
|
+
// Copy copilot-instructions.md for GitHub Copilot
|
|
1109
|
+
const copilotMdPath = pathModule.join(__dirname, '..', 'abap', 'copilot-instructions.md');
|
|
1110
|
+
const githubDir = pathModule.join(process.cwd(), '.github');
|
|
1111
|
+
const localCopilotMdPath = pathModule.join(githubDir, 'copilot-instructions.md');
|
|
1112
|
+
try {
|
|
1113
|
+
if (fs.existsSync(copilotMdPath)) {
|
|
1114
|
+
// Ensure .github directory exists
|
|
1115
|
+
if (!fs.existsSync(githubDir)) {
|
|
1116
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
1117
|
+
}
|
|
1118
|
+
fs.copyFileSync(copilotMdPath, localCopilotMdPath);
|
|
1119
|
+
console.log(`✅ Created .github/copilot-instructions.md`);
|
|
1120
|
+
} else {
|
|
1121
|
+
console.log(`⚠️ copilot-instructions.md not found in abap/ directory`);
|
|
1122
|
+
}
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
console.error(`Error copying copilot-instructions.md: ${error.message}`);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
821
1127
|
// Create folder
|
|
822
1128
|
const folderPath = pathModule.join(process.cwd(), folder);
|
|
823
1129
|
try {
|
|
@@ -1144,8 +1450,12 @@ Examples:
|
|
|
1144
1450
|
const config = loadConfig();
|
|
1145
1451
|
const csrfToken = await fetchCsrfToken(config);
|
|
1146
1452
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1453
|
+
// Send all files in one request
|
|
1454
|
+
const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config);
|
|
1455
|
+
|
|
1456
|
+
// Process results
|
|
1457
|
+
for (const result of results) {
|
|
1458
|
+
await processInspectResult(result);
|
|
1149
1459
|
}
|
|
1150
1460
|
break;
|
|
1151
1461
|
}
|
|
@@ -1173,6 +1483,249 @@ Examples:
|
|
|
1173
1483
|
break;
|
|
1174
1484
|
}
|
|
1175
1485
|
|
|
1486
|
+
case 'tree': {
|
|
1487
|
+
const packageArgIndex = args.indexOf('--package');
|
|
1488
|
+
if (packageArgIndex === -1 || packageArgIndex + 1 >= args.length) {
|
|
1489
|
+
console.error('Error: --package parameter required');
|
|
1490
|
+
console.error('Usage: abapgit-agent tree --package <package> [--depth <n>] [--include-objects] [--json]');
|
|
1491
|
+
console.error('Example: abapgit-agent tree --package $ZMY_PACKAGE');
|
|
1492
|
+
process.exit(1);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const packageName = args[packageArgIndex + 1];
|
|
1496
|
+
|
|
1497
|
+
// Optional depth parameter
|
|
1498
|
+
const depthArgIndex = args.indexOf('--depth');
|
|
1499
|
+
let depth = 3;
|
|
1500
|
+
if (depthArgIndex !== -1 && depthArgIndex + 1 < args.length) {
|
|
1501
|
+
depth = parseInt(args[depthArgIndex + 1], 10);
|
|
1502
|
+
if (isNaN(depth) || depth < 1) {
|
|
1503
|
+
console.error('Error: --depth must be a positive number');
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// Optional include-objects parameter
|
|
1509
|
+
const includeObjects = args.includes('--include-objects');
|
|
1510
|
+
|
|
1511
|
+
// Optional json parameter
|
|
1512
|
+
const jsonOutput = args.includes('--json');
|
|
1513
|
+
|
|
1514
|
+
if (jsonOutput) {
|
|
1515
|
+
const config = loadConfig();
|
|
1516
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
1517
|
+
const result = await runTreeCommand(packageName, depth, includeObjects, csrfToken, config);
|
|
1518
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1519
|
+
} else {
|
|
1520
|
+
await displayTreeOutput(packageName, depth, includeObjects);
|
|
1521
|
+
}
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
case 'view': {
|
|
1526
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
1527
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
1528
|
+
console.error('Error: --objects parameter required');
|
|
1529
|
+
console.error('Usage: abapgit-agent view --objects <obj1>,<obj2>,... [--type <type>] [--json]');
|
|
1530
|
+
console.error('Example: abapgit-agent view --objects ZCL_MY_CLASS');
|
|
1531
|
+
console.error('Example: abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --type CLAS');
|
|
1532
|
+
process.exit(1);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
1536
|
+
const typeArg = args.indexOf('--type');
|
|
1537
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
1538
|
+
const jsonOutput = args.includes('--json');
|
|
1539
|
+
|
|
1540
|
+
console.log(`\n Viewing ${objects.length} object(s)`);
|
|
1541
|
+
|
|
1542
|
+
const config = loadConfig();
|
|
1543
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
1544
|
+
|
|
1545
|
+
const data = {
|
|
1546
|
+
objects: objects
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
if (type) {
|
|
1550
|
+
data.type = type;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/view', data, { csrfToken });
|
|
1554
|
+
|
|
1555
|
+
// Handle uppercase keys from ABAP
|
|
1556
|
+
const success = result.SUCCESS || result.success;
|
|
1557
|
+
const viewObjects = result.OBJECTS || result.objects || [];
|
|
1558
|
+
const message = result.MESSAGE || result.message || '';
|
|
1559
|
+
const error = result.ERROR || result.error;
|
|
1560
|
+
|
|
1561
|
+
if (!success || error) {
|
|
1562
|
+
console.error(`\n Error: ${error || 'Failed to view objects'}`);
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
if (jsonOutput) {
|
|
1567
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1568
|
+
} else {
|
|
1569
|
+
console.log(`\n ${message}`);
|
|
1570
|
+
console.log('');
|
|
1571
|
+
|
|
1572
|
+
for (let i = 0; i < viewObjects.length; i++) {
|
|
1573
|
+
const obj = viewObjects[i];
|
|
1574
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
1575
|
+
const objType = obj.TYPE || obj.type || '';
|
|
1576
|
+
const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
|
|
1577
|
+
const description = obj.DESCRIPTION || obj.description || '';
|
|
1578
|
+
const methods = obj.METHODS || obj.methods || [];
|
|
1579
|
+
const components = obj.COMPONENTS || obj.components || [];
|
|
1580
|
+
const notFound = obj.NOT_FOUND || obj.not_found || false;
|
|
1581
|
+
|
|
1582
|
+
// Check if object was not found
|
|
1583
|
+
if (notFound) {
|
|
1584
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
1585
|
+
console.log(` Object not found: ${objName}`);
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
console.log(` 📖 ${objName} (${objTypeText})`);
|
|
1590
|
+
if (description) {
|
|
1591
|
+
console.log(` ${description}`);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// Display source code for classes, interfaces, and CDS views
|
|
1595
|
+
const source = obj.SOURCE || obj.source || '';
|
|
1596
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
|
|
1597
|
+
console.log('');
|
|
1598
|
+
// Replace escaped newlines with actual newlines and display
|
|
1599
|
+
const displaySource = source.replace(/\\n/g, '\n');
|
|
1600
|
+
const lines = displaySource.split('\n');
|
|
1601
|
+
for (const line of lines) {
|
|
1602
|
+
console.log(` ${line}`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if (methods.length > 0) {
|
|
1607
|
+
console.log(` Methods: ${methods.length}`);
|
|
1608
|
+
for (const method of methods.slice(0, 5)) {
|
|
1609
|
+
const name = method.NAME || method.name || '';
|
|
1610
|
+
const visibility = method.VISIBILITY || method.visibility || '';
|
|
1611
|
+
console.log(` - ${visibility} ${name}`);
|
|
1612
|
+
}
|
|
1613
|
+
if (methods.length > 5) {
|
|
1614
|
+
console.log(` ... and ${methods.length - 5} more`);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
if (components.length > 0) {
|
|
1619
|
+
// Check if this is a data element (DTEL) - show domain info in property format
|
|
1620
|
+
if (objType === 'DTEL' || objType === 'Data Element') {
|
|
1621
|
+
const propWidth = 18;
|
|
1622
|
+
const valueWidth = 40;
|
|
1623
|
+
|
|
1624
|
+
// Build separator with corners
|
|
1625
|
+
const sep = '┌' + '─'.repeat(propWidth + 2) + '┬' + '─'.repeat(valueWidth + 2) + '┐';
|
|
1626
|
+
const mid = '├' + '─'.repeat(propWidth + 2) + '┼' + '─'.repeat(valueWidth + 2) + '┤';
|
|
1627
|
+
const end = '└' + '─'.repeat(propWidth + 2) + '┴' + '─'.repeat(valueWidth + 2) + '┘';
|
|
1628
|
+
|
|
1629
|
+
// Helper to build row
|
|
1630
|
+
const buildPropRow = (property, value) => {
|
|
1631
|
+
return '│ ' + String(property || '').padEnd(propWidth) + ' │ ' +
|
|
1632
|
+
String(value || '').substring(0, valueWidth).padEnd(valueWidth) + ' │';
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
console.log(` DATA ELEMENT ${objName}:`);
|
|
1636
|
+
console.log(sep);
|
|
1637
|
+
console.log(buildPropRow('Property', 'Value'));
|
|
1638
|
+
console.log(mid);
|
|
1639
|
+
|
|
1640
|
+
// Collect properties from top-level fields and components
|
|
1641
|
+
const domain = obj.DOMAIN || obj.domain || '';
|
|
1642
|
+
const domainType = obj.DOMAIN_TYPE || obj.domain_type || '';
|
|
1643
|
+
const domainLength = obj.DOMAIN_LENGTH || obj.domain_length || 0;
|
|
1644
|
+
const domainDecimals = obj.DOMAIN_DECIMALS || obj.domain_decimals || 0;
|
|
1645
|
+
const description = obj.DESCRIPTION || obj.description || '';
|
|
1646
|
+
|
|
1647
|
+
if (domainType) {
|
|
1648
|
+
console.log(buildPropRow('Data Type', domainType));
|
|
1649
|
+
}
|
|
1650
|
+
if (domainLength) {
|
|
1651
|
+
console.log(buildPropRow('Length', String(domainLength)));
|
|
1652
|
+
}
|
|
1653
|
+
if (domainDecimals) {
|
|
1654
|
+
console.log(buildPropRow('Decimals', String(domainDecimals)));
|
|
1655
|
+
}
|
|
1656
|
+
if (description) {
|
|
1657
|
+
console.log(buildPropRow('Description', description));
|
|
1658
|
+
}
|
|
1659
|
+
if (domain) {
|
|
1660
|
+
console.log(buildPropRow('Domain', domain));
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
console.log(end);
|
|
1664
|
+
} else if (objType === 'TTYP' || objType === 'Table Type') {
|
|
1665
|
+
// Show TTYP details as simple text lines
|
|
1666
|
+
console.log('');
|
|
1667
|
+
for (const comp of components) {
|
|
1668
|
+
const desc = comp.DESCRIPTION || comp.description || '';
|
|
1669
|
+
if (desc) {
|
|
1670
|
+
console.log(` ${desc}`);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
} else {
|
|
1674
|
+
// Build table display for TABL/STRU with Data Element and Description
|
|
1675
|
+
const colWidths = {
|
|
1676
|
+
field: 16, // Max field name length
|
|
1677
|
+
key: 3,
|
|
1678
|
+
type: 8,
|
|
1679
|
+
length: 8,
|
|
1680
|
+
dataelement: 30, // Max data element name length
|
|
1681
|
+
description: 60, // Max field description length
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// Helper to truncate with ellipsis if needed
|
|
1685
|
+
const truncate = (str, maxLen) => {
|
|
1686
|
+
const s = String(str || '');
|
|
1687
|
+
if (s.length <= maxLen) return s;
|
|
1688
|
+
return s.substring(0, maxLen - 1) + '…';
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
// Helper to build row
|
|
1692
|
+
const buildRow = (field, key, type, length, dataelement, description) => {
|
|
1693
|
+
return ' | ' + truncate(field, colWidths.field).padEnd(colWidths.field) + ' | ' + String(key || '').padEnd(colWidths.key) + ' | ' + truncate(type, colWidths.type).padEnd(colWidths.type) + ' | ' + String(length || '').padStart(colWidths.length) + ' | ' + truncate(dataelement, colWidths.dataelement).padEnd(colWidths.dataelement) + ' | ' + truncate(description, colWidths.description).padEnd(colWidths.description) + ' |';
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
// Build separator line (matches row structure with | at ends and + between columns)
|
|
1697
|
+
const sep = ' |' + '-'.repeat(colWidths.field + 2) + '+' +
|
|
1698
|
+
'-'.repeat(colWidths.key + 2) + '+' +
|
|
1699
|
+
'-'.repeat(colWidths.type + 2) + '+' +
|
|
1700
|
+
'-'.repeat(colWidths.length + 2) + '+' +
|
|
1701
|
+
'-'.repeat(colWidths.dataelement + 2) + '+' +
|
|
1702
|
+
'-'.repeat(colWidths.description + 2) + '|';
|
|
1703
|
+
|
|
1704
|
+
// Header
|
|
1705
|
+
console.log(` TABLE ${objName}:`);
|
|
1706
|
+
console.log(sep);
|
|
1707
|
+
console.log(buildRow('Field', 'Key', 'Type', 'Length', 'Data Elem', 'Description'));
|
|
1708
|
+
console.log(sep);
|
|
1709
|
+
|
|
1710
|
+
// Rows
|
|
1711
|
+
for (const comp of components) {
|
|
1712
|
+
const key = comp.KEY || comp.key || false ? 'X' : '';
|
|
1713
|
+
const dataelement = comp.DATAELEMENT || comp.dataelement || '';
|
|
1714
|
+
const description = comp.DESCRIPTION || comp.description || '';
|
|
1715
|
+
console.log(buildRow(comp.FIELD || comp.field, key, comp.TYPE || comp.type, comp.LENGTH || comp.length, dataelement, description));
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
console.log(sep);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
console.log('');
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
}
|
|
1726
|
+
break;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1176
1729
|
case 'help':
|
|
1177
1730
|
case '--help':
|
|
1178
1731
|
case '-h':
|
|
@@ -1206,6 +1759,12 @@ Commands:
|
|
|
1206
1759
|
unit --files <file1>,<file2>,...
|
|
1207
1760
|
Run AUnit tests for ABAP test class files (.testclasses.abap)
|
|
1208
1761
|
|
|
1762
|
+
tree --package <package> [--depth <n>] [--include-objects] [--json]
|
|
1763
|
+
Display package hierarchy tree from ABAP system
|
|
1764
|
+
|
|
1765
|
+
view --objects <obj1>,<obj2>,... [--type <type>] [--json]
|
|
1766
|
+
View ABAP object definitions from the ABAP system
|
|
1767
|
+
|
|
1209
1768
|
health
|
|
1210
1769
|
Check if ABAP REST API is healthy
|
|
1211
1770
|
|
|
@@ -1223,6 +1782,14 @@ Examples:
|
|
|
1223
1782
|
abapgit-agent pull --transport DEVK900001 # With transport
|
|
1224
1783
|
abapgit-agent inspect --files zcl_my_class.clas.abap # Syntax check
|
|
1225
1784
|
abapgit-agent unit --files zcl_my_test.clas.testclasses.abap # Run tests
|
|
1785
|
+
abapgit-agent tree --package $ZMY_PACKAGE # Show package tree
|
|
1786
|
+
abapgit-agent tree --package $ZMY_PACKAGE --depth 2 # Shallow tree
|
|
1787
|
+
abapgit-agent tree --package $ZMY_PACKAGE --include-objects # With object counts
|
|
1788
|
+
abapgit-agent tree --package $ZMY_PACKAGE --json # JSON output
|
|
1789
|
+
abapgit-agent view --objects ZCL_MY_CLASS # View class definition
|
|
1790
|
+
abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
|
|
1791
|
+
abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
|
|
1792
|
+
abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
|
|
1226
1793
|
abapgit-agent health
|
|
1227
1794
|
abapgit-agent status
|
|
1228
1795
|
`);
|