abapgit-agent 1.7.2 → 1.8.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/README.md +7 -7
- package/abap/CLAUDE.md +145 -26
- package/abap/guidelines/00_index.md +8 -0
- package/abap/guidelines/01_sql.md +8 -0
- package/abap/guidelines/02_exceptions.md +8 -0
- package/abap/guidelines/03_testing.md +8 -0
- package/abap/guidelines/04_cds.md +8 -0
- package/abap/guidelines/05_classes.md +8 -0
- package/abap/guidelines/06_objects.md +8 -0
- package/abap/guidelines/07_json.md +8 -0
- package/abap/guidelines/08_abapgit.md +8 -0
- package/abap/guidelines/09_unit_testable_code.md +8 -0
- package/bin/abapgit-agent +61 -2852
- package/package.json +21 -5
- package/src/agent.js +205 -16
- package/src/commands/create.js +102 -0
- package/src/commands/delete.js +72 -0
- package/src/commands/health.js +24 -0
- package/src/commands/help.js +111 -0
- package/src/commands/import.js +99 -0
- package/src/commands/init.js +321 -0
- package/src/commands/inspect.js +184 -0
- package/src/commands/list.js +143 -0
- package/src/commands/preview.js +277 -0
- package/src/commands/pull.js +278 -0
- package/src/commands/ref.js +96 -0
- package/src/commands/status.js +52 -0
- package/src/commands/syntax.js +290 -0
- package/src/commands/tree.js +209 -0
- package/src/commands/unit.js +133 -0
- package/src/commands/view.js +215 -0
- package/src/commands/where.js +138 -0
- package/src/config.js +11 -1
- package/src/utils/abap-http.js +347 -0
- package/src/utils/git-utils.js +58 -0
- package/src/utils/validators.js +72 -0
- package/src/utils/version-check.js +80 -0
- package/src/abap-client.js +0 -526
- /package/src/{ref-search.js → utils/abap-reference.js} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit command - Run AUnit tests for ABAP test class files
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const pathModule = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run unit test for a single file
|
|
10
|
+
*/
|
|
11
|
+
async function runUnitTestForFile(sourceFile, csrfToken, config, coverage, http) {
|
|
12
|
+
console.log(` Running unit test for: ${sourceFile}`);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Read file content
|
|
16
|
+
const absolutePath = pathModule.isAbsolute(sourceFile)
|
|
17
|
+
? sourceFile
|
|
18
|
+
: pathModule.join(process.cwd(), sourceFile);
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(absolutePath)) {
|
|
21
|
+
console.error(` ❌ File not found: ${absolutePath}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Extract object type and name from file path
|
|
26
|
+
// e.g., "zcl_my_test.clas.abap" -> CLAS, ZCL_MY_TEST
|
|
27
|
+
const fileName = pathModule.basename(sourceFile).toUpperCase();
|
|
28
|
+
const parts = fileName.split('.');
|
|
29
|
+
if (parts.length < 3) {
|
|
30
|
+
console.error(` ❌ Invalid file format: ${sourceFile}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// obj_name is first part (may contain path), obj_type is second part
|
|
35
|
+
const objType = parts[1] === 'CLASS' ? 'CLAS' : parts[1];
|
|
36
|
+
let objName = parts[0];
|
|
37
|
+
|
|
38
|
+
// Handle subdirectory paths
|
|
39
|
+
const lastSlash = objName.lastIndexOf('/');
|
|
40
|
+
if (lastSlash >= 0) {
|
|
41
|
+
objName = objName.substring(lastSlash + 1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Send files array to unit endpoint (ABAP expects string_table of file names)
|
|
45
|
+
const data = {
|
|
46
|
+
files: [sourceFile],
|
|
47
|
+
coverage: coverage
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/unit', data, { csrfToken });
|
|
51
|
+
|
|
52
|
+
// Handle uppercase keys from ABAP
|
|
53
|
+
const success = result.SUCCESS || result.success;
|
|
54
|
+
const testCount = result.TEST_COUNT || result.test_count || 0;
|
|
55
|
+
const passedCount = result.PASSED_COUNT || result.passed_count || 0;
|
|
56
|
+
const failedCount = result.FAILED_COUNT || result.failed_count || 0;
|
|
57
|
+
const message = result.MESSAGE || result.message || '';
|
|
58
|
+
const errors = result.ERRORS || result.errors || [];
|
|
59
|
+
|
|
60
|
+
// Handle coverage data
|
|
61
|
+
const coverageStats = result.COVERAGE_STATS || result.coverage_stats;
|
|
62
|
+
|
|
63
|
+
if (testCount === 0) {
|
|
64
|
+
console.log(` ➖ ${objName} - No unit tests`);
|
|
65
|
+
} else if (success === 'X' || success === true) {
|
|
66
|
+
console.log(` ✅ ${objName} - All tests passed`);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(` ❌ ${objName} - Tests failed`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(` Tests: ${testCount} | Passed: ${passedCount} | Failed: ${failedCount}`);
|
|
72
|
+
|
|
73
|
+
// Display coverage if available
|
|
74
|
+
if (coverage && coverageStats) {
|
|
75
|
+
const totalLines = coverageStats.TOTAL_LINES || coverageStats.total_lines || 0;
|
|
76
|
+
const coveredLines = coverageStats.COVERED_LINES || coverageStats.covered_lines || 0;
|
|
77
|
+
const coverageRate = coverageStats.COVERAGE_RATE || coverageStats.coverage_rate || 0;
|
|
78
|
+
|
|
79
|
+
console.log(` 📊 Coverage: ${coverageRate}%`);
|
|
80
|
+
console.log(` Total Lines: ${totalLines}`);
|
|
81
|
+
console.log(` Covered Lines: ${coveredLines}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (failedCount > 0 && errors.length > 0) {
|
|
85
|
+
for (const err of errors) {
|
|
86
|
+
const className = err.CLASS_NAME || err.class_name || '?';
|
|
87
|
+
const methodName = err.METHOD_NAME || err.method_name || '?';
|
|
88
|
+
const errorText = err.ERROR_TEXT || err.error_text || 'Unknown error';
|
|
89
|
+
console.log(` ✗ ${className}=>${methodName}: ${errorText}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(`\n Error: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
name: 'unit',
|
|
101
|
+
description: 'Run AUnit tests for ABAP test class files',
|
|
102
|
+
requiresAbapConfig: true,
|
|
103
|
+
requiresVersionCheck: true,
|
|
104
|
+
|
|
105
|
+
async execute(args, context) {
|
|
106
|
+
const { loadConfig, AbapHttp } = context;
|
|
107
|
+
|
|
108
|
+
const filesArgIndex = args.indexOf('--files');
|
|
109
|
+
if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
|
|
110
|
+
console.error('Error: --files parameter required');
|
|
111
|
+
console.error('Usage: abapgit-agent unit --files <file1>,<file2>,... [--coverage]');
|
|
112
|
+
console.error('Example: abapgit-agent unit --files src/zcl_my_test.clas.abap');
|
|
113
|
+
console.error('Example: abapgit-agent unit --files src/zcl_my_test.clas.abap --coverage');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const files = args[filesArgIndex + 1].split(',').map(f => f.trim());
|
|
118
|
+
|
|
119
|
+
// Check for coverage option
|
|
120
|
+
const coverage = args.includes('--coverage');
|
|
121
|
+
|
|
122
|
+
console.log(`\n Running unit tests for ${files.length} file(s)${coverage ? ' (with coverage)' : ''}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
|
|
125
|
+
const config = loadConfig();
|
|
126
|
+
const http = new AbapHttp(config);
|
|
127
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
128
|
+
|
|
129
|
+
for (const sourceFile of files) {
|
|
130
|
+
await runUnitTestForFile(sourceFile, csrfToken, config, coverage, http);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View command - View ABAP object definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'view',
|
|
7
|
+
description: 'View ABAP object definitions from ABAP system',
|
|
8
|
+
requiresAbapConfig: true,
|
|
9
|
+
requiresVersionCheck: true,
|
|
10
|
+
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const { loadConfig, AbapHttp } = context;
|
|
13
|
+
|
|
14
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
15
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
16
|
+
console.error('Error: --objects parameter required');
|
|
17
|
+
console.error('Usage: abapgit-agent view --objects <obj1>,<obj2>,... [--type <type>] [--json]');
|
|
18
|
+
console.error('Example: abapgit-agent view --objects ZCL_MY_CLASS');
|
|
19
|
+
console.error('Example: abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --type CLAS');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
24
|
+
const typeArg = args.indexOf('--type');
|
|
25
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
26
|
+
const jsonOutput = args.includes('--json');
|
|
27
|
+
|
|
28
|
+
console.log(`\n Viewing ${objects.length} object(s)`);
|
|
29
|
+
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
const http = new AbapHttp(config);
|
|
32
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
33
|
+
|
|
34
|
+
const data = {
|
|
35
|
+
objects: objects
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (type) {
|
|
39
|
+
data.type = type;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/view', data, { csrfToken });
|
|
43
|
+
|
|
44
|
+
// Handle uppercase keys from ABAP
|
|
45
|
+
const success = result.SUCCESS || result.success;
|
|
46
|
+
const viewObjects = result.OBJECTS || result.objects || [];
|
|
47
|
+
const message = result.MESSAGE || result.message || '';
|
|
48
|
+
const error = result.ERROR || result.error;
|
|
49
|
+
|
|
50
|
+
if (!success || error) {
|
|
51
|
+
console.error(`\n Error: ${error || 'Failed to view objects'}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (jsonOutput) {
|
|
56
|
+
console.log(JSON.stringify(result, null, 2));
|
|
57
|
+
} else {
|
|
58
|
+
console.log(`\n ${message}`);
|
|
59
|
+
console.log('');
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < viewObjects.length; i++) {
|
|
62
|
+
const obj = viewObjects[i];
|
|
63
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
64
|
+
const objType = obj.TYPE || obj.type || '';
|
|
65
|
+
const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
|
|
66
|
+
const description = obj.DESCRIPTION || obj.description || '';
|
|
67
|
+
const methods = obj.METHODS || obj.methods || [];
|
|
68
|
+
const components = obj.COMPONENTS || obj.components || [];
|
|
69
|
+
const notFound = obj.NOT_FOUND || obj.not_found || false;
|
|
70
|
+
|
|
71
|
+
// Check if object was not found
|
|
72
|
+
if (notFound) {
|
|
73
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
74
|
+
console.log(` Object not found: ${objName}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(` 📖 ${objName} (${objTypeText})`);
|
|
79
|
+
if (description) {
|
|
80
|
+
console.log(` ${description}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Display source code for classes, interfaces, CDS views, and programs/source includes
|
|
84
|
+
const source = obj.SOURCE || obj.source || '';
|
|
85
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program')) {
|
|
86
|
+
console.log('');
|
|
87
|
+
// Replace escaped newlines with actual newlines and display
|
|
88
|
+
const displaySource = source.replace(/\\n/g, '\n');
|
|
89
|
+
const lines = displaySource.split('\n');
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
console.log(` ${line}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (methods.length > 0) {
|
|
96
|
+
console.log(` Methods: ${methods.length}`);
|
|
97
|
+
for (const method of methods.slice(0, 5)) {
|
|
98
|
+
const name = method.NAME || method.name || '';
|
|
99
|
+
const visibility = method.VISIBILITY || method.visibility || '';
|
|
100
|
+
console.log(` - ${visibility} ${name}`);
|
|
101
|
+
}
|
|
102
|
+
if (methods.length > 5) {
|
|
103
|
+
console.log(` ... and ${methods.length - 5} more`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (components.length > 0) {
|
|
108
|
+
// Check if this is a data element (DTEL) - show domain info in property format
|
|
109
|
+
if (objType === 'DTEL' || objType === 'Data Element') {
|
|
110
|
+
const propWidth = 18;
|
|
111
|
+
const valueWidth = 40;
|
|
112
|
+
|
|
113
|
+
// Build separator with corners
|
|
114
|
+
const sep = '┌' + '─'.repeat(propWidth + 2) + '┬' + '─'.repeat(valueWidth + 2) + '┐';
|
|
115
|
+
const mid = '├' + '─'.repeat(propWidth + 2) + '┼' + '─'.repeat(valueWidth + 2) + '┤';
|
|
116
|
+
const end = '└' + '─'.repeat(propWidth + 2) + '┴' + '─'.repeat(valueWidth + 2) + '┘';
|
|
117
|
+
|
|
118
|
+
// Helper to build row
|
|
119
|
+
const buildPropRow = (property, value) => {
|
|
120
|
+
return '│ ' + String(property || '').padEnd(propWidth) + ' │ ' +
|
|
121
|
+
String(value || '').substring(0, valueWidth).padEnd(valueWidth) + ' │';
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
console.log(` DATA ELEMENT ${objName}:`);
|
|
125
|
+
console.log(sep);
|
|
126
|
+
console.log(buildPropRow('Property', 'Value'));
|
|
127
|
+
console.log(mid);
|
|
128
|
+
|
|
129
|
+
// Collect properties from top-level fields and components
|
|
130
|
+
const domain = obj.DOMAIN || obj.domain || '';
|
|
131
|
+
const domainType = obj.DOMAIN_TYPE || obj.domain_type || '';
|
|
132
|
+
const domainLength = obj.DOMAIN_LENGTH || obj.domain_length || 0;
|
|
133
|
+
const domainDecimals = obj.DOMAIN_DECIMALS || obj.domain_decimals || 0;
|
|
134
|
+
const description = obj.DESCRIPTION || obj.description || '';
|
|
135
|
+
|
|
136
|
+
if (domainType) {
|
|
137
|
+
console.log(buildPropRow('Data Type', domainType));
|
|
138
|
+
}
|
|
139
|
+
if (domainLength) {
|
|
140
|
+
console.log(buildPropRow('Length', String(domainLength)));
|
|
141
|
+
}
|
|
142
|
+
if (domainDecimals) {
|
|
143
|
+
console.log(buildPropRow('Decimals', String(domainDecimals)));
|
|
144
|
+
}
|
|
145
|
+
if (description) {
|
|
146
|
+
console.log(buildPropRow('Description', description));
|
|
147
|
+
}
|
|
148
|
+
if (domain) {
|
|
149
|
+
console.log(buildPropRow('Domain', domain));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(end);
|
|
153
|
+
} else if (objType === 'TTYP' || objType === 'Table Type') {
|
|
154
|
+
// Show TTYP details as simple text lines
|
|
155
|
+
console.log('');
|
|
156
|
+
for (const comp of components) {
|
|
157
|
+
const desc = comp.DESCRIPTION || comp.description || '';
|
|
158
|
+
if (desc) {
|
|
159
|
+
console.log(` ${desc}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// Build table display for TABL/STRU with Data Element and Description
|
|
164
|
+
const colWidths = {
|
|
165
|
+
field: 16, // Max field name length
|
|
166
|
+
key: 3,
|
|
167
|
+
type: 8,
|
|
168
|
+
length: 8,
|
|
169
|
+
dataelement: 30, // Max data element name length
|
|
170
|
+
description: 60, // Max field description length
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Helper to truncate with ellipsis if needed
|
|
174
|
+
const truncate = (str, maxLen) => {
|
|
175
|
+
const s = String(str || '');
|
|
176
|
+
if (s.length <= maxLen) return s;
|
|
177
|
+
return s.substring(0, maxLen - 1) + '…';
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Helper to build row
|
|
181
|
+
const buildRow = (field, key, type, length, dataelement, description) => {
|
|
182
|
+
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) + ' |';
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Build separator line (matches row structure with | at ends and + between columns)
|
|
186
|
+
const sep = ' |' + '-'.repeat(colWidths.field + 2) + '+' +
|
|
187
|
+
'-'.repeat(colWidths.key + 2) + '+' +
|
|
188
|
+
'-'.repeat(colWidths.type + 2) + '+' +
|
|
189
|
+
'-'.repeat(colWidths.length + 2) + '+' +
|
|
190
|
+
'-'.repeat(colWidths.dataelement + 2) + '+' +
|
|
191
|
+
'-'.repeat(colWidths.description + 2) + '|';
|
|
192
|
+
|
|
193
|
+
// Header
|
|
194
|
+
console.log(` TABLE ${objName}:`);
|
|
195
|
+
console.log(sep);
|
|
196
|
+
console.log(buildRow('Field', 'Key', 'Type', 'Length', 'Data Elem', 'Description'));
|
|
197
|
+
console.log(sep);
|
|
198
|
+
|
|
199
|
+
// Rows
|
|
200
|
+
for (const comp of components) {
|
|
201
|
+
const key = comp.KEY || comp.key || false ? 'X' : '';
|
|
202
|
+
const dataelement = comp.DATAELEMENT || comp.dataelement || '';
|
|
203
|
+
const description = comp.DESCRIPTION || comp.description || '';
|
|
204
|
+
console.log(buildRow(comp.FIELD || comp.field, key, comp.TYPE || comp.type, comp.LENGTH || comp.length, dataelement, description));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(sep);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log('');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where command - Find where-used references for ABAP objects
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'where',
|
|
7
|
+
description: 'Find where-used references for ABAP objects',
|
|
8
|
+
requiresAbapConfig: true,
|
|
9
|
+
requiresVersionCheck: true,
|
|
10
|
+
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const { loadConfig, AbapHttp } = context;
|
|
13
|
+
|
|
14
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
15
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
16
|
+
console.error('Error: --objects parameter required');
|
|
17
|
+
console.error('Usage: abapgit-agent where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--offset <n>] [--json]');
|
|
18
|
+
console.error('Example: abapgit-agent where --objects ZCL_MY_CLASS');
|
|
19
|
+
console.error('Example: abapgit-agent where --objects ZIF_MY_INTERFACE');
|
|
20
|
+
console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20');
|
|
21
|
+
console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --offset 100');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
26
|
+
const typeArg = args.indexOf('--type');
|
|
27
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
28
|
+
const limitArg = args.indexOf('--limit');
|
|
29
|
+
const limitRaw = limitArg !== -1 ? args[limitArg + 1] : null;
|
|
30
|
+
const limitParsed = parseInt(limitRaw, 10);
|
|
31
|
+
const limit = limitRaw && !limitRaw.startsWith('--') && !isNaN(limitParsed) ? Math.max(1, limitParsed) : 100;
|
|
32
|
+
const offsetArg = args.indexOf('--offset');
|
|
33
|
+
const offsetRaw = offsetArg !== -1 ? args[offsetArg + 1] : null;
|
|
34
|
+
const offsetParsed = parseInt(offsetRaw, 10);
|
|
35
|
+
const offset = offsetRaw && !offsetRaw.startsWith('--') && !isNaN(offsetParsed) ? Math.max(0, offsetParsed) : 0;
|
|
36
|
+
const jsonOutput = args.includes('--json');
|
|
37
|
+
|
|
38
|
+
console.log(`\n Where-used list for ${objects.length} object(s)`);
|
|
39
|
+
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
const http = new AbapHttp(config);
|
|
42
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
43
|
+
|
|
44
|
+
const data = {
|
|
45
|
+
objects: objects,
|
|
46
|
+
limit: Math.min(Math.max(1, limit), 500),
|
|
47
|
+
offset: Math.max(0, offset)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (type) {
|
|
51
|
+
data.type = type;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/where', data, { csrfToken });
|
|
55
|
+
|
|
56
|
+
// Handle uppercase keys from ABAP
|
|
57
|
+
const success = result.SUCCESS || result.success;
|
|
58
|
+
const whereObjects = result.OBJECTS || result.objects || [];
|
|
59
|
+
const message = result.MESSAGE || result.message || '';
|
|
60
|
+
const error = result.ERROR || result.error;
|
|
61
|
+
const pagination = result.PAGINATION || result.pagination || null;
|
|
62
|
+
|
|
63
|
+
if (!success || error) {
|
|
64
|
+
console.error(`\n Error: ${error || 'Failed to get where-used list'}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (jsonOutput) {
|
|
69
|
+
console.log(JSON.stringify(result, null, 2));
|
|
70
|
+
} else {
|
|
71
|
+
// Build pagination message
|
|
72
|
+
let paginationMsg = '';
|
|
73
|
+
const paginationTotal = pagination.TOTAL || pagination.total || 0;
|
|
74
|
+
const paginationHasMore = pagination.HAS_MORE || pagination.has_more || false;
|
|
75
|
+
if (pagination && paginationTotal > 0) {
|
|
76
|
+
// Handle case where offset exceeds total
|
|
77
|
+
if (offset >= paginationTotal) {
|
|
78
|
+
paginationMsg = ` (Offset ${offset} exceeds total ${paginationTotal})`;
|
|
79
|
+
} else {
|
|
80
|
+
const start = offset + 1;
|
|
81
|
+
const end = Math.min(offset + limit, paginationTotal);
|
|
82
|
+
paginationMsg = ` (Showing ${start}-${end} of ${paginationTotal})`;
|
|
83
|
+
if (paginationHasMore) {
|
|
84
|
+
paginationMsg += ` — Use --offset ${offset + limit} to see more`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`\n ${message}${paginationMsg}`);
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < whereObjects.length; i++) {
|
|
93
|
+
const obj = whereObjects[i];
|
|
94
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
95
|
+
const objType = obj.TYPE || obj.type || '';
|
|
96
|
+
const error = obj.ERROR || obj.error || '';
|
|
97
|
+
const references = obj.REFERENCES || obj.references || [];
|
|
98
|
+
const count = obj.COUNT || obj.count || 0;
|
|
99
|
+
|
|
100
|
+
// Handle object not found error
|
|
101
|
+
if (error) {
|
|
102
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
103
|
+
console.log(` ${error}`);
|
|
104
|
+
console.log('');
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (count === 0) {
|
|
109
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
110
|
+
console.log(` No references found`);
|
|
111
|
+
console.log('');
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(` 🔍 ${objName} (${objType})`);
|
|
116
|
+
console.log(` Found ${count} reference(s):`);
|
|
117
|
+
console.log('');
|
|
118
|
+
|
|
119
|
+
// Display references - one line format: include → method (type) or include (type)
|
|
120
|
+
for (let j = 0; j < references.length; j++) {
|
|
121
|
+
const ref = references[j];
|
|
122
|
+
const includeName = ref.INCLUDE_NAME || ref.include_name || '';
|
|
123
|
+
const includeType = ref.INCLUDE_TYPE || ref.include_type || '';
|
|
124
|
+
const methodName = ref.METHOD_NAME || ref.method_name || '';
|
|
125
|
+
|
|
126
|
+
let line;
|
|
127
|
+
if (methodName) {
|
|
128
|
+
line = ` ${j + 1}. ${includeName} → ${methodName} (${includeType})`;
|
|
129
|
+
} else {
|
|
130
|
+
line = ` ${j + 1}. ${includeName} (${includeType})`;
|
|
131
|
+
}
|
|
132
|
+
console.log(line);
|
|
133
|
+
}
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
package/src/config.js
CHANGED
|
@@ -65,9 +65,19 @@ function getTransport() {
|
|
|
65
65
|
return cfg.transport;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Check if ABAP integration is configured for this repo
|
|
70
|
+
* @returns {boolean} True if .abapGitAgent file exists
|
|
71
|
+
*/
|
|
72
|
+
function isAbapIntegrationEnabled() {
|
|
73
|
+
const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
|
|
74
|
+
return fs.existsSync(repoConfigPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
module.exports = {
|
|
69
78
|
loadConfig,
|
|
70
79
|
getAbapConfig,
|
|
71
80
|
getAgentConfig,
|
|
72
|
-
getTransport
|
|
81
|
+
getTransport,
|
|
82
|
+
isAbapIntegrationEnabled
|
|
73
83
|
};
|