abapgit-agent 1.7.1 → 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/.abapGitAgent.example +11 -0
- package/README.md +7 -7
- package/abap/.github/copilot-instructions.md +254 -0
- package/abap/CLAUDE.md +432 -0
- 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 -2789
- package/package.json +25 -5
- package/src/agent.js +213 -20
- 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/{ref-search.js → utils/abap-reference.js} +119 -1
- 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 -523
|
@@ -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
|
};
|