abapgit-agent 1.6.0 → 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/abap/guidelines/00_index.md +1 -0
- package/abap/guidelines/01_sql.md +16 -0
- package/abap/guidelines/02_exceptions.md +92 -0
- package/abap/guidelines/03_testing.md +36 -0
- package/abap/guidelines/04_cds.md +16 -0
- package/abap/guidelines/05_classes.md +119 -0
- package/abap/guidelines/06_objects.md +46 -86
- package/abap/guidelines/07_json.md +2 -0
- package/abap/guidelines/08_abapgit.md +40 -0
- package/abap/guidelines/09_unit_testable_code.md +589 -0
- package/bin/abapgit-agent +176 -32
- package/package.json +5 -1
- package/src/abap-client.js +46 -0
- package/src/agent.js +48 -0
- package/src/config.js +1 -1
- package/src/ref-search.js +92 -44
package/bin/abapgit-agent
CHANGED
|
@@ -538,12 +538,14 @@ async function processInspectResult(res) {
|
|
|
538
538
|
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
539
539
|
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
540
540
|
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
541
|
+
const infos = res.INFOS !== undefined ? res.INFOS : (res.infos || []);
|
|
541
542
|
|
|
542
|
-
if (errorCount > 0 || warnings.length > 0) {
|
|
543
|
+
if (errorCount > 0 || warnings.length > 0 || infos.length > 0) {
|
|
543
544
|
if (errorCount > 0) {
|
|
544
545
|
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
545
546
|
} else {
|
|
546
|
-
|
|
547
|
+
const total = warnings.length + infos.length;
|
|
548
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${total}):`);
|
|
547
549
|
}
|
|
548
550
|
console.log('\nErrors:');
|
|
549
551
|
console.log('─'.repeat(60));
|
|
@@ -552,8 +554,16 @@ async function processInspectResult(res) {
|
|
|
552
554
|
const line = err.LINE || err.line || '?';
|
|
553
555
|
const column = err.COLUMN || err.column || '?';
|
|
554
556
|
const text = err.TEXT || err.text || 'Unknown error';
|
|
557
|
+
const methodName = err.METHOD_NAME || err.method_name;
|
|
558
|
+
const sobjname = err.SOBJNAME || err.sobjname;
|
|
555
559
|
|
|
560
|
+
if (methodName) {
|
|
561
|
+
console.log(` Method: ${methodName}`);
|
|
562
|
+
}
|
|
556
563
|
console.log(` Line ${line}, Column ${column}:`);
|
|
564
|
+
if (sobjname && sobjname.includes('====')) {
|
|
565
|
+
console.log(` Include: ${sobjname}`);
|
|
566
|
+
}
|
|
557
567
|
console.log(` ${text}`);
|
|
558
568
|
console.log('');
|
|
559
569
|
}
|
|
@@ -565,7 +575,38 @@ async function processInspectResult(res) {
|
|
|
565
575
|
for (const warn of warnings) {
|
|
566
576
|
const line = warn.LINE || warn.line || '?';
|
|
567
577
|
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
568
|
-
|
|
578
|
+
const methodName = warn.METHOD_NAME || warn.method_name;
|
|
579
|
+
const sobjname = warn.SOBJNAME || warn.sobjname;
|
|
580
|
+
|
|
581
|
+
if (methodName) {
|
|
582
|
+
console.log(` Method: ${methodName}`);
|
|
583
|
+
}
|
|
584
|
+
console.log(` Line ${line}:`);
|
|
585
|
+
if (sobjname && sobjname.includes('====')) {
|
|
586
|
+
console.log(` Include: ${sobjname}`);
|
|
587
|
+
}
|
|
588
|
+
console.log(` ${text}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Show infos if any
|
|
593
|
+
if (infos.length > 0) {
|
|
594
|
+
console.log('Info:');
|
|
595
|
+
console.log('─'.repeat(60));
|
|
596
|
+
for (const info of infos) {
|
|
597
|
+
const line = info.LINE || info.line || '?';
|
|
598
|
+
const text = info.MESSAGE || info.message || 'Unknown info';
|
|
599
|
+
const methodName = info.METHOD_NAME || info.method_name;
|
|
600
|
+
const sobjname = info.SOBJNAME || info.sobjname;
|
|
601
|
+
|
|
602
|
+
if (methodName) {
|
|
603
|
+
console.log(` Method: ${methodName}`);
|
|
604
|
+
}
|
|
605
|
+
console.log(` Line ${line}:`);
|
|
606
|
+
if (sobjname && sobjname.includes('====')) {
|
|
607
|
+
console.log(` Include: ${sobjname}`);
|
|
608
|
+
}
|
|
609
|
+
console.log(` ${text}`);
|
|
569
610
|
}
|
|
570
611
|
}
|
|
571
612
|
} else if (success === true || success === 'X') {
|
|
@@ -652,7 +693,7 @@ async function runUnitTests(options) {
|
|
|
652
693
|
/**
|
|
653
694
|
* Run unit test for a single file
|
|
654
695
|
*/
|
|
655
|
-
async function runUnitTestForFile(sourceFile, csrfToken, config) {
|
|
696
|
+
async function runUnitTestForFile(sourceFile, csrfToken, config, coverage = false) {
|
|
656
697
|
console.log(` Running unit test for: ${sourceFile}`);
|
|
657
698
|
|
|
658
699
|
try {
|
|
@@ -687,7 +728,8 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
|
|
|
687
728
|
|
|
688
729
|
// Send files array to unit endpoint (ABAP expects string_table of file names)
|
|
689
730
|
const data = {
|
|
690
|
-
files: [sourceFile]
|
|
731
|
+
files: [sourceFile],
|
|
732
|
+
coverage: coverage
|
|
691
733
|
};
|
|
692
734
|
|
|
693
735
|
const result = await request('POST', '/sap/bc/z_abapgit_agent/unit', data, { csrfToken });
|
|
@@ -700,6 +742,9 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
|
|
|
700
742
|
const message = result.MESSAGE || result.message || '';
|
|
701
743
|
const errors = result.ERRORS || result.errors || [];
|
|
702
744
|
|
|
745
|
+
// Handle coverage data
|
|
746
|
+
const coverageStats = result.COVERAGE_STATS || result.coverage_stats;
|
|
747
|
+
|
|
703
748
|
if (testCount === 0) {
|
|
704
749
|
console.log(` ➖ ${objName} - No unit tests`);
|
|
705
750
|
} else if (success === 'X' || success === true) {
|
|
@@ -710,6 +755,17 @@ async function runUnitTestForFile(sourceFile, csrfToken, config) {
|
|
|
710
755
|
|
|
711
756
|
console.log(` Tests: ${testCount} | Passed: ${passedCount} | Failed: ${failedCount}`);
|
|
712
757
|
|
|
758
|
+
// Display coverage if available
|
|
759
|
+
if (coverage && coverageStats) {
|
|
760
|
+
const totalLines = coverageStats.TOTAL_LINES || coverageStats.total_lines || 0;
|
|
761
|
+
const coveredLines = coverageStats.COVERED_LINES || coverageStats.covered_lines || 0;
|
|
762
|
+
const coverageRate = coverageStats.COVERAGE_RATE || coverageStats.coverage_rate || 0;
|
|
763
|
+
|
|
764
|
+
console.log(` 📊 Coverage: ${coverageRate}%`);
|
|
765
|
+
console.log(` Total Lines: ${totalLines}`);
|
|
766
|
+
console.log(` Covered Lines: ${coveredLines}`);
|
|
767
|
+
}
|
|
768
|
+
|
|
713
769
|
if (failedCount > 0 && errors.length > 0) {
|
|
714
770
|
for (const err of errors) {
|
|
715
771
|
const className = err.CLASS_NAME || err.class_name || '?';
|
|
@@ -1727,21 +1783,25 @@ Examples:
|
|
|
1727
1783
|
const filesArgIndex = args.indexOf('--files');
|
|
1728
1784
|
if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
|
|
1729
1785
|
console.error('Error: --files parameter required');
|
|
1730
|
-
console.error('Usage: abapgit-agent unit --files <file1>,<file2>,...');
|
|
1786
|
+
console.error('Usage: abapgit-agent unit --files <file1>,<file2>,... [--coverage]');
|
|
1731
1787
|
console.error('Example: abapgit-agent unit --files zcl_my_test.clas.abap');
|
|
1788
|
+
console.error('Example: abapgit-agent unit --files zcl_my_test.clas.abap --coverage');
|
|
1732
1789
|
process.exit(1);
|
|
1733
1790
|
}
|
|
1734
1791
|
|
|
1735
1792
|
const files = args[filesArgIndex + 1].split(',').map(f => f.trim());
|
|
1736
1793
|
|
|
1737
|
-
|
|
1794
|
+
// Check for coverage option
|
|
1795
|
+
const coverage = args.includes('--coverage');
|
|
1796
|
+
|
|
1797
|
+
console.log(`\n Running unit tests for ${files.length} file(s)${coverage ? ' (with coverage)' : ''}`);
|
|
1738
1798
|
console.log('');
|
|
1739
1799
|
|
|
1740
1800
|
const config = loadConfig();
|
|
1741
1801
|
const csrfToken = await fetchCsrfToken(config);
|
|
1742
1802
|
|
|
1743
1803
|
for (const sourceFile of files) {
|
|
1744
|
-
await runUnitTestForFile(sourceFile, csrfToken, config);
|
|
1804
|
+
await runUnitTestForFile(sourceFile, csrfToken, config, coverage);
|
|
1745
1805
|
}
|
|
1746
1806
|
break;
|
|
1747
1807
|
}
|
|
@@ -2007,9 +2067,9 @@ Examples:
|
|
|
2007
2067
|
console.log(` ${description}`);
|
|
2008
2068
|
}
|
|
2009
2069
|
|
|
2010
|
-
// Display source code for classes, interfaces, and
|
|
2070
|
+
// Display source code for classes, interfaces, CDS views, and programs/source includes
|
|
2011
2071
|
const source = obj.SOURCE || obj.source || '';
|
|
2012
|
-
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
|
|
2072
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program')) {
|
|
2013
2073
|
console.log('');
|
|
2014
2074
|
// Replace escaped newlines with actual newlines and display
|
|
2015
2075
|
const displaySource = source.replace(/\\n/g, '\n');
|
|
@@ -2379,6 +2439,105 @@ Examples:
|
|
|
2379
2439
|
break;
|
|
2380
2440
|
}
|
|
2381
2441
|
|
|
2442
|
+
case 'where': {
|
|
2443
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
2444
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
2445
|
+
console.error('Error: --objects parameter required');
|
|
2446
|
+
console.error('Usage: abapgit-agent where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]');
|
|
2447
|
+
console.error('Example: abapgit-agent where --objects ZCL_MY_CLASS');
|
|
2448
|
+
console.error('Example: abapgit-agent where --objects ZIF_MY_INTERFACE');
|
|
2449
|
+
console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20');
|
|
2450
|
+
process.exit(1);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
2454
|
+
const typeArg = args.indexOf('--type');
|
|
2455
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
2456
|
+
const limitArg = args.indexOf('--limit');
|
|
2457
|
+
const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 100;
|
|
2458
|
+
const jsonOutput = args.includes('--json');
|
|
2459
|
+
|
|
2460
|
+
console.log(`\n Where-used list for ${objects.length} object(s)`);
|
|
2461
|
+
|
|
2462
|
+
const config = loadConfig();
|
|
2463
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
2464
|
+
|
|
2465
|
+
const data = {
|
|
2466
|
+
objects: objects,
|
|
2467
|
+
limit: Math.min(Math.max(1, limit), 500)
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
if (type) {
|
|
2471
|
+
data.type = type;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/where', data, { csrfToken });
|
|
2475
|
+
|
|
2476
|
+
// Handle uppercase keys from ABAP
|
|
2477
|
+
const success = result.SUCCESS || result.success;
|
|
2478
|
+
const whereObjects = result.OBJECTS || result.objects || [];
|
|
2479
|
+
const message = result.MESSAGE || result.message || '';
|
|
2480
|
+
const error = result.ERROR || result.error;
|
|
2481
|
+
|
|
2482
|
+
if (!success || error) {
|
|
2483
|
+
console.error(`\n Error: ${error || 'Failed to get where-used list'}`);
|
|
2484
|
+
break;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
if (jsonOutput) {
|
|
2488
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2489
|
+
} else {
|
|
2490
|
+
console.log(`\n ${message}`);
|
|
2491
|
+
console.log('');
|
|
2492
|
+
|
|
2493
|
+
for (let i = 0; i < whereObjects.length; i++) {
|
|
2494
|
+
const obj = whereObjects[i];
|
|
2495
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
2496
|
+
const objType = obj.TYPE || obj.type || '';
|
|
2497
|
+
const error = obj.ERROR || obj.error || '';
|
|
2498
|
+
const references = obj.REFERENCES || obj.references || [];
|
|
2499
|
+
const count = obj.COUNT || obj.count || 0;
|
|
2500
|
+
|
|
2501
|
+
// Handle object not found error
|
|
2502
|
+
if (error) {
|
|
2503
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2504
|
+
console.log(` ${error}`);
|
|
2505
|
+
console.log('');
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
if (count === 0) {
|
|
2510
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2511
|
+
console.log(` No references found`);
|
|
2512
|
+
console.log('');
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
console.log(` 🔍 ${objName} (${objType})`);
|
|
2517
|
+
console.log(` Found ${count} reference(s):`);
|
|
2518
|
+
console.log('');
|
|
2519
|
+
|
|
2520
|
+
// Display references - one line format: include → method (type) or include (type)
|
|
2521
|
+
for (let j = 0; j < references.length; j++) {
|
|
2522
|
+
const ref = references[j];
|
|
2523
|
+
const includeName = ref.INCLUDE_NAME || ref.include_name || '';
|
|
2524
|
+
const includeType = ref.INCLUDE_TYPE || ref.include_type || '';
|
|
2525
|
+
const methodName = ref.METHOD_NAME || ref.method_name || '';
|
|
2526
|
+
|
|
2527
|
+
let line;
|
|
2528
|
+
if (methodName) {
|
|
2529
|
+
line = ` ${j + 1}. ${includeName} → ${methodName} (${includeType})`;
|
|
2530
|
+
} else {
|
|
2531
|
+
line = ` ${j + 1}. ${includeName} (${includeType})`;
|
|
2532
|
+
}
|
|
2533
|
+
console.log(line);
|
|
2534
|
+
}
|
|
2535
|
+
console.log('');
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2382
2541
|
case 'ref': {
|
|
2383
2542
|
const refSearch = require('../src/ref-search');
|
|
2384
2543
|
const topicIndex = args.indexOf('--topic');
|
|
@@ -2452,32 +2611,10 @@ Examples:
|
|
|
2452
2611
|
const pattern = args[patternIndex];
|
|
2453
2612
|
const result = await refSearch.searchPattern(pattern);
|
|
2454
2613
|
|
|
2455
|
-
// Also search guidelines if available
|
|
2456
|
-
const guidelinesResult = await refSearch.searchGuidelines(pattern);
|
|
2457
|
-
if (guidelinesResult && guidelinesResult.guidelinesFound && guidelinesResult.matches.length > 0) {
|
|
2458
|
-
result.guidelines = guidelinesResult;
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
2614
|
if (jsonOutput) {
|
|
2462
2615
|
console.log(JSON.stringify(result, null, 2));
|
|
2463
2616
|
} else {
|
|
2464
2617
|
refSearch.displaySearchResults(result);
|
|
2465
|
-
// Display guidelines results if found
|
|
2466
|
-
if (guidelinesResult && guidelinesResult.guidelinesFound && guidelinesResult.matches.length > 0) {
|
|
2467
|
-
console.log('\n 📋 Custom Guidelines:');
|
|
2468
|
-
for (const match of guidelinesResult.matches.slice(0, 5)) {
|
|
2469
|
-
console.log(` 📄 ${match.file} (line ${match.line}):`);
|
|
2470
|
-
const lines = match.context.split('\n');
|
|
2471
|
-
lines.forEach((line, idx) => {
|
|
2472
|
-
const prefix = idx === 1 ? ' → ' : ' ';
|
|
2473
|
-
const trimmed = line.slice(0, 80);
|
|
2474
|
-
console.log(`${prefix}${trimmed}`);
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2477
|
-
if (guidelinesResult.matches.length > 5) {
|
|
2478
|
-
console.log(` ... and ${guidelinesResult.matches.length - 5} more matches`);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
2618
|
}
|
|
2482
2619
|
break;
|
|
2483
2620
|
}
|
|
@@ -2526,6 +2663,9 @@ Commands:
|
|
|
2526
2663
|
view --objects <obj1>,<obj2>,... [--type <type>] [--json]
|
|
2527
2664
|
View ABAP object definitions from the ABAP system
|
|
2528
2665
|
|
|
2666
|
+
where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]
|
|
2667
|
+
Find where-used list for ABAP objects (classes, interfaces, programs)
|
|
2668
|
+
|
|
2529
2669
|
ref <pattern> [--json]
|
|
2530
2670
|
Search ABAP reference repositories for patterns. Requires referenceFolder in .abapGitAgent.
|
|
2531
2671
|
|
|
@@ -2572,6 +2712,10 @@ Examples:
|
|
|
2572
2712
|
abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
|
|
2573
2713
|
abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
|
|
2574
2714
|
abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
|
|
2715
|
+
abapgit-agent where --objects ZCL_SUT_AUNIT_RUNNER # Find where class is used
|
|
2716
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE # Find interface implementations
|
|
2717
|
+
abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20 # Limit results
|
|
2718
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE --json # JSON output
|
|
2575
2719
|
abapgit-agent ref "CORRESPONDING" # Search all reference repos
|
|
2576
2720
|
abapgit-agent ref "CX_SY_" # Search exceptions
|
|
2577
2721
|
abapgit-agent ref --topic exceptions # View exception topic
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"start": "node src/server.js",
|
|
17
17
|
"dev": "nodemon src/server.js",
|
|
18
18
|
"test": "jest",
|
|
19
|
+
"test:all": "node scripts/test-all.js",
|
|
20
|
+
"test:jest": "jest",
|
|
21
|
+
"test:aunit": "node scripts/test-all.js --jest --cmd",
|
|
22
|
+
"test:cmd": "node scripts/test-all.js --jest --aunit",
|
|
19
23
|
"pull": "node bin/abapgit-agent",
|
|
20
24
|
"release": "node scripts/release.js",
|
|
21
25
|
"unrelease": "node scripts/unrelease.js"
|
package/src/abap-client.js
CHANGED
|
@@ -459,6 +459,52 @@ class ABAPClient {
|
|
|
459
459
|
|
|
460
460
|
return await this.request('POST', '/list', data, { csrfToken: this.csrfToken });
|
|
461
461
|
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* View ABAP object definitions
|
|
465
|
+
* @param {Array} objects - Array of object names to view
|
|
466
|
+
* @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
|
|
467
|
+
* @returns {object} View result with object definitions
|
|
468
|
+
*/
|
|
469
|
+
async view(objects, type = null) {
|
|
470
|
+
await this.fetchCsrfToken();
|
|
471
|
+
|
|
472
|
+
const data = {
|
|
473
|
+
objects: objects
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
if (type) {
|
|
477
|
+
data.type = type;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
logger.info('Viewing objects', { objects, type, service: 'abapgit-agent' });
|
|
481
|
+
|
|
482
|
+
return await this.request('POST', '/view', data, { csrfToken: this.csrfToken });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Find where-used list for ABAP objects
|
|
487
|
+
* @param {Array} objects - Array of object names to search
|
|
488
|
+
* @param {string} type - Object type (optional)
|
|
489
|
+
* @param {number} limit - Maximum results (default: 100, max: 500)
|
|
490
|
+
* @returns {object} Where-used result with found objects
|
|
491
|
+
*/
|
|
492
|
+
async where(objects, type = null, limit = 100) {
|
|
493
|
+
await this.fetchCsrfToken();
|
|
494
|
+
|
|
495
|
+
const data = {
|
|
496
|
+
objects: objects,
|
|
497
|
+
limit: Math.min(Math.max(1, limit), 500)
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
if (type) {
|
|
501
|
+
data.type = type;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
logger.info('Finding where-used', { objects, type, limit: data.limit, service: 'abapgit-agent' });
|
|
505
|
+
|
|
506
|
+
return await this.request('POST', '/where', data, { csrfToken: this.csrfToken });
|
|
507
|
+
}
|
|
462
508
|
}
|
|
463
509
|
|
|
464
510
|
// Singleton instance
|
package/src/agent.js
CHANGED
|
@@ -264,6 +264,54 @@ class ABAPGitAgent {
|
|
|
264
264
|
throw new Error(`List command failed: ${error.message}`);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* View ABAP object definitions
|
|
270
|
+
* @param {Array} objects - Array of object names to view
|
|
271
|
+
* @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
|
|
272
|
+
* @returns {object} View result with object definitions
|
|
273
|
+
*/
|
|
274
|
+
async view(objects, type = null) {
|
|
275
|
+
logger.info('Viewing objects', { objects, type });
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const result = await this.abap.view(objects, type);
|
|
279
|
+
return {
|
|
280
|
+
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
281
|
+
command: result.COMMAND || result.command || 'VIEW',
|
|
282
|
+
objects: result.OBJECTS || result.objects || [],
|
|
283
|
+
error: result.ERROR || result.error || null
|
|
284
|
+
};
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('View command failed', { error: error.message });
|
|
287
|
+
throw new Error(`View command failed: ${error.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Find where-used list for ABAP objects
|
|
293
|
+
* @param {Array} objects - Array of object names to search
|
|
294
|
+
* @param {string} type - Object type (optional)
|
|
295
|
+
* @param {number} limit - Maximum results (default: 100, max: 500)
|
|
296
|
+
* @returns {object} Where-used result with found objects
|
|
297
|
+
*/
|
|
298
|
+
async where(objects, type = null, limit = 100) {
|
|
299
|
+
logger.info('Finding where-used', { objects, type, limit });
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const result = await this.abap.where(objects, type, limit);
|
|
303
|
+
return {
|
|
304
|
+
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
305
|
+
command: result.COMMAND || result.command || 'WHERE',
|
|
306
|
+
objects: result.OBJECTS || result.objects || [],
|
|
307
|
+
message: result.MESSAGE || result.message || '',
|
|
308
|
+
error: result.ERROR || result.error || null
|
|
309
|
+
};
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logger.error('Where command failed', { error: error.message });
|
|
312
|
+
throw new Error(`Where command failed: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
267
315
|
}
|
|
268
316
|
|
|
269
317
|
module.exports = {
|
package/src/config.js
CHANGED
|
@@ -24,7 +24,7 @@ function loadConfig() {
|
|
|
24
24
|
if (fs.existsSync(configPath)) {
|
|
25
25
|
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
26
26
|
} else {
|
|
27
|
-
// Load from environment variables
|
|
27
|
+
// Load from environment variables only when no config file exists
|
|
28
28
|
config = {
|
|
29
29
|
host: process.env.ABAP_HOST,
|
|
30
30
|
sapport: parseInt(process.env.ABAP_PORT, 10) || 443,
|
package/src/ref-search.js
CHANGED
|
@@ -257,79 +257,117 @@ async function getSearchableFiles(repoPath, repoName, extensions = ['.md', '.aba
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
/**
|
|
260
|
-
* Search for a pattern across all reference repositories
|
|
260
|
+
* Search for a pattern across all reference repositories and local guidelines
|
|
261
261
|
* @param {string} pattern - Pattern to search for
|
|
262
262
|
* @returns {Promise<Object>} Search results
|
|
263
263
|
*/
|
|
264
264
|
async function searchPattern(pattern) {
|
|
265
265
|
const refFolder = detectReferenceFolder();
|
|
266
266
|
const repos = await getReferenceRepositories();
|
|
267
|
+
const guidelinesFolder = detectGuidelinesFolder();
|
|
267
268
|
|
|
268
|
-
|
|
269
|
+
// If neither reference folder nor guidelines exist, return error
|
|
270
|
+
if (!refFolder && !guidelinesFolder) {
|
|
269
271
|
return {
|
|
270
272
|
error: 'Reference folder not found',
|
|
271
|
-
hint: 'Configure referenceFolder in .abapGitAgent
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (repos.length === 0) {
|
|
276
|
-
return {
|
|
277
|
-
error: 'No ABAP repositories found in reference folder',
|
|
278
|
-
hint: 'Clone ABAP repositories to the reference folder to enable searching'
|
|
273
|
+
hint: 'Configure referenceFolder in .abapGitAgent, clone to ~/abap-reference, or create abap/guidelines/ folder'
|
|
279
274
|
};
|
|
280
275
|
}
|
|
281
276
|
|
|
282
277
|
const results = {
|
|
283
278
|
pattern,
|
|
284
279
|
referenceFolder: refFolder,
|
|
280
|
+
guidelinesFolder: guidelinesFolder,
|
|
285
281
|
repositories: repos.map(r => r.name),
|
|
286
282
|
files: [],
|
|
287
283
|
matches: []
|
|
288
284
|
};
|
|
289
285
|
|
|
290
286
|
try {
|
|
291
|
-
// Search
|
|
292
|
-
|
|
293
|
-
const
|
|
287
|
+
// Search reference repositories if available
|
|
288
|
+
if (repos.length > 0) {
|
|
289
|
+
for (const repo of repos) {
|
|
290
|
+
const searchableFiles = await getSearchableFiles(repo.path, repo.name);
|
|
291
|
+
|
|
292
|
+
for (const fileInfo of searchableFiles) {
|
|
293
|
+
try {
|
|
294
|
+
const content = await readFile(fileInfo.path, 'utf8');
|
|
295
|
+
|
|
296
|
+
if (content.toLowerCase().includes(pattern.toLowerCase())) {
|
|
297
|
+
results.files.push({
|
|
298
|
+
repo: repo.name,
|
|
299
|
+
file: fileInfo.relativePath
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Find matching lines with context
|
|
303
|
+
const lines = content.split('\n');
|
|
304
|
+
let matchCount = 0;
|
|
305
|
+
|
|
306
|
+
for (let i = 0; i < lines.length; i++) {
|
|
307
|
+
if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
|
|
308
|
+
const start = Math.max(0, i - 1);
|
|
309
|
+
const end = Math.min(lines.length, i + 2);
|
|
310
|
+
const context = lines.slice(start, end).join('\n');
|
|
311
|
+
|
|
312
|
+
results.matches.push({
|
|
313
|
+
repo: repo.name,
|
|
314
|
+
file: fileInfo.relativePath,
|
|
315
|
+
line: i + 1,
|
|
316
|
+
context
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
matchCount++;
|
|
320
|
+
|
|
321
|
+
// Limit matches per file to avoid overwhelming output
|
|
322
|
+
if (matchCount >= 3) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
// Skip files we can't read
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
294
334
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
335
|
+
// Search local guidelines folder if available
|
|
336
|
+
if (guidelinesFolder) {
|
|
337
|
+
const guidelineFiles = await getGuidelineFiles();
|
|
298
338
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
339
|
+
for (const file of guidelineFiles) {
|
|
340
|
+
if (file.content.toLowerCase().includes(pattern.toLowerCase())) {
|
|
341
|
+
results.files.push({
|
|
342
|
+
repo: 'guidelines',
|
|
343
|
+
file: file.relativePath
|
|
344
|
+
});
|
|
304
345
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
346
|
+
// Find matching lines with context
|
|
347
|
+
const lines = file.content.split('\n');
|
|
348
|
+
let matchCount = 0;
|
|
308
349
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
350
|
+
for (let i = 0; i < lines.length; i++) {
|
|
351
|
+
if (lines[i].toLowerCase().includes(pattern.toLowerCase())) {
|
|
352
|
+
const start = Math.max(0, i - 1);
|
|
353
|
+
const end = Math.min(lines.length, i + 2);
|
|
354
|
+
const context = lines.slice(start, end).join('\n');
|
|
314
355
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
356
|
+
results.matches.push({
|
|
357
|
+
repo: 'guidelines',
|
|
358
|
+
file: file.relativePath,
|
|
359
|
+
line: i + 1,
|
|
360
|
+
context
|
|
361
|
+
});
|
|
321
362
|
|
|
322
|
-
|
|
363
|
+
matchCount++;
|
|
323
364
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
365
|
+
// Limit matches per file to avoid overwhelming output
|
|
366
|
+
if (matchCount >= 3) {
|
|
367
|
+
break;
|
|
328
368
|
}
|
|
329
369
|
}
|
|
330
370
|
}
|
|
331
|
-
} catch (error) {
|
|
332
|
-
// Skip files we can't read
|
|
333
371
|
}
|
|
334
372
|
}
|
|
335
373
|
}
|
|
@@ -469,7 +507,16 @@ function displaySearchResults(results) {
|
|
|
469
507
|
}
|
|
470
508
|
|
|
471
509
|
console.log(`\n 🔍 Searching for: '${results.pattern}'`);
|
|
472
|
-
|
|
510
|
+
|
|
511
|
+
// Show which sources were searched
|
|
512
|
+
const sources = [];
|
|
513
|
+
if (results.referenceFolder) {
|
|
514
|
+
sources.push('reference repositories');
|
|
515
|
+
}
|
|
516
|
+
if (results.guidelinesFolder) {
|
|
517
|
+
sources.push('local guidelines');
|
|
518
|
+
}
|
|
519
|
+
console.log(` 📁 Sources searched: ${sources.join(', ') || 'none'}`);
|
|
473
520
|
|
|
474
521
|
if (results.repositories && results.repositories.length > 0) {
|
|
475
522
|
console.log(` 📚 Repositories (${results.repositories.length}): ${results.repositories.join(', ')}`);
|
|
@@ -491,7 +538,8 @@ function displaySearchResults(results) {
|
|
|
491
538
|
|
|
492
539
|
console.log(` ✅ Found in ${results.files.length} file(s):`);
|
|
493
540
|
for (const [repo, files] of Object.entries(filesByRepo)) {
|
|
494
|
-
|
|
541
|
+
const icon = repo === 'guidelines' ? '📋' : '📦';
|
|
542
|
+
console.log(`\n ${icon} ${repo}/`);
|
|
495
543
|
files.forEach(file => {
|
|
496
544
|
console.log(` • ${file}`);
|
|
497
545
|
});
|