abapgit-agent 1.6.1 → 1.7.1
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 +10 -0
- package/abap/guidelines/02_exceptions.md +24 -0
- package/abap/guidelines/03_testing.md +19 -0
- package/abap/guidelines/05_classes.md +111 -0
- package/abap/guidelines/06_objects.md +45 -92
- package/abap/guidelines/08_abapgit.md +11 -0
- package/abap/guidelines/09_unit_testable_code.md +21 -0
- package/bin/abapgit-agent +210 -4
- package/package.json +5 -1
- package/src/abap-client.js +46 -0
- package/src/agent.js +48 -0
package/README.md
CHANGED
|
@@ -52,6 +52,9 @@ abapgit-agent init --folder /abap/ --package ZMY_PACKAGE
|
|
|
52
52
|
# Create online repository in ABAP
|
|
53
53
|
abapgit-agent create
|
|
54
54
|
|
|
55
|
+
# Delete online repository from ABAP (keeps local files)
|
|
56
|
+
abapgit-agent delete
|
|
57
|
+
|
|
55
58
|
# Import objects from ABAP package to git
|
|
56
59
|
abapgit-agent import
|
|
57
60
|
```
|
|
@@ -113,6 +116,11 @@ abapgit-agent preview --objects SFLIGHT --where "CARRID = 'AA'"
|
|
|
113
116
|
abapgit-agent preview --objects SFLIGHT --columns CARRID,CONNID,PRICE
|
|
114
117
|
abapgit-agent preview --objects SFLIGHT --vertical
|
|
115
118
|
abapgit-agent preview --objects SFLIGHT --compact
|
|
119
|
+
|
|
120
|
+
# Find where-used (objects using a specific object)
|
|
121
|
+
abapgit-agent where --objects ZCL_MY_CLASS
|
|
122
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE
|
|
123
|
+
abapgit-agent where --objects ZCL_MY_CLASS --type CLAS
|
|
116
124
|
```
|
|
117
125
|
|
|
118
126
|
### Utility Commands
|
|
@@ -147,6 +155,7 @@ npm run pull -- --url <git-url> --branch main
|
|
|
147
155
|
| Installation & Setup | [INSTALL.md](INSTALL.md) |
|
|
148
156
|
| init Command | [docs/init-command.md](docs/init-command.md) |
|
|
149
157
|
| create Command | [docs/create-command.md](docs/create-command.md) |
|
|
158
|
+
| delete Command | [docs/delete-command.md](docs/delete-command.md) |
|
|
150
159
|
| import Command | [docs/import-command.md](docs/import-command.md) |
|
|
151
160
|
| pull Command | [docs/pull-command.md](docs/pull-command.md) |
|
|
152
161
|
| inspect Command | [docs/inspect-command.md](docs/inspect-command.md) |
|
|
@@ -154,6 +163,7 @@ npm run pull -- --url <git-url> --branch main
|
|
|
154
163
|
| tree Command | [docs/tree-command.md](docs/tree-command.md) |
|
|
155
164
|
| view Command | [docs/view-command.md](docs/view-command.md) |
|
|
156
165
|
| preview Command | [docs/preview-command.md](docs/preview-command.md) |
|
|
166
|
+
| where Command | [docs/where-command.md](docs/where-command.md) |
|
|
157
167
|
| ref Command | [docs/ref-command.md](docs/ref-command.md) |
|
|
158
168
|
| REST API Reference | [API.md](API.md) |
|
|
159
169
|
| Error Handling | [ERROR_HANDLING.md](ERROR_HANDLING.md) |
|
|
@@ -174,3 +174,27 @@ ENDTRY.
|
|
|
174
174
|
| Multiple callers need to handle exception | Add RAISING to method definition |
|
|
175
175
|
| Exception should be handled internally | Use TRY-CATCH in implementation |
|
|
176
176
|
| Interface method | Add RAISING to interface, or use TRY-CATCH in class |
|
|
177
|
+
|
|
178
|
+
### RAISING Clause in Method Calls
|
|
179
|
+
|
|
180
|
+
The `RAISING` clause **cannot be used in method call statements**. It can only be used in method definitions.
|
|
181
|
+
|
|
182
|
+
```abap
|
|
183
|
+
" WRONG - syntax error
|
|
184
|
+
lo_handler->read( ... ) RAISING cx_dd_ddl_check.
|
|
185
|
+
|
|
186
|
+
" CORRECT - use TRY-CATCH
|
|
187
|
+
TRY.
|
|
188
|
+
lo_handler->read( ... ).
|
|
189
|
+
CATCH cx_dd_ddl_check.
|
|
190
|
+
" Handle error
|
|
191
|
+
ENDTRY.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Correct usage in method definitions:**
|
|
195
|
+
```abap
|
|
196
|
+
" Interface/Class method definition
|
|
197
|
+
METHODS read
|
|
198
|
+
IMPORTING iv_name TYPE string
|
|
199
|
+
RAISING cx_static_check.
|
|
200
|
+
``` |
|
|
@@ -79,6 +79,25 @@ Examples of compliant names:
|
|
|
79
79
|
- `test_exec_files` (16 chars)
|
|
80
80
|
- `test_interface` (15 chars)
|
|
81
81
|
|
|
82
|
+
### Test Methods and RAISING Clause
|
|
83
|
+
|
|
84
|
+
If a test method calls methods that raise exceptions, add `RAISING` to the method definition:
|
|
85
|
+
|
|
86
|
+
```abap
|
|
87
|
+
" CORRECT - declare that method can raise exceptions
|
|
88
|
+
METHODS test_validate_ddls FOR TESTING RAISING cx_static_check.
|
|
89
|
+
METHODS test_read_data FOR TESTING RAISING cx_dd_ddl_check.
|
|
90
|
+
|
|
91
|
+
" Then implement with TRY-CATCH if needed
|
|
92
|
+
METHOD test_validate_ddls.
|
|
93
|
+
TRY.
|
|
94
|
+
mo_cut->some_method( ).
|
|
95
|
+
CATCH cx_static_check.
|
|
96
|
+
" Handle exception
|
|
97
|
+
ENDTRY.
|
|
98
|
+
ENDMETHOD.
|
|
99
|
+
```
|
|
100
|
+
|
|
82
101
|
### Common Assertions
|
|
83
102
|
|
|
84
103
|
```abap
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
2. Constructor - line 20
|
|
8
8
|
3. Interfaces - line 35
|
|
9
9
|
4. Inline Declaration - line 50
|
|
10
|
+
5. Abstract Methods - line 99
|
|
11
|
+
6. FINAL Class Limitation - line 117
|
|
12
|
+
7. Working with TYPE any - line 135
|
|
10
13
|
|
|
11
14
|
## ABAP Class Definition - Must Use PUBLIC
|
|
12
15
|
|
|
@@ -56,3 +59,111 @@ ENDCLASS.
|
|
|
56
59
|
|
|
57
60
|
**Wrong**: `METHOD do_something.` - parameter `iv_param` will be unknown
|
|
58
61
|
**Correct**: `METHOD zif_my_interface~do_something.` - parameters recognized
|
|
62
|
+
|
|
63
|
+
## Use Interface Type for References
|
|
64
|
+
|
|
65
|
+
When a class implements an interface, use the **interface type** instead of the class type for references:
|
|
66
|
+
|
|
67
|
+
```abap
|
|
68
|
+
" Interface definition
|
|
69
|
+
INTERFACE zif_my_interface PUBLIC.
|
|
70
|
+
METHODS do_something RETURNING VALUE(rv_result) TYPE string.
|
|
71
|
+
ENDINTERFACE.
|
|
72
|
+
|
|
73
|
+
" Class implements interface
|
|
74
|
+
CLASS zcl_my_class DEFINITION PUBLIC.
|
|
75
|
+
PUBLIC SECTION.
|
|
76
|
+
INTERFACES zif_my_interface.
|
|
77
|
+
CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zif_my_interface.
|
|
78
|
+
ENDCLASS.
|
|
79
|
+
|
|
80
|
+
" Caller - use interface type, not class type
|
|
81
|
+
CLASS zcl_consumer DEFINITION PUBLIC.
|
|
82
|
+
PRIVATE SECTION.
|
|
83
|
+
DATA mo_instance TYPE REF TO zif_my_interface. " <- Use interface type
|
|
84
|
+
ENDCLASS.
|
|
85
|
+
|
|
86
|
+
METHOD zcl_consumer->do_something.
|
|
87
|
+
mo_instance = zcl_my_class=>get_instance( ).
|
|
88
|
+
|
|
89
|
+
" Call without interface prefix - cleaner code
|
|
90
|
+
DATA(lv_result) = mo_instance->do_something( ).
|
|
91
|
+
ENDMETHOD.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Benefits:**
|
|
95
|
+
- Cleaner code: `mo_instance->method( )` instead of `mo_instance->zif_my_interface~method( )`
|
|
96
|
+
- Flexibility: Can swap implementation class without changing caller (dependency inversion)
|
|
97
|
+
- Consistency: All callers use the same interface type
|
|
98
|
+
|
|
99
|
+
**Key rule:** Always use `REF TO zif_xxx` not `REF TO zcl_xxx` for instance variables and parameters.
|
|
100
|
+
|
|
101
|
+
## Abstract Methods
|
|
102
|
+
|
|
103
|
+
The ABSTRACT keyword must come immediately after the method name:
|
|
104
|
+
|
|
105
|
+
```abap
|
|
106
|
+
" ✅ Correct - ABSTRACT right after method name
|
|
107
|
+
METHODS get_name ABSTRACT
|
|
108
|
+
RETURNING VALUE(rv_name) TYPE string.
|
|
109
|
+
|
|
110
|
+
" ❌ Wrong - ABSTRACT after parameters (syntax error)
|
|
111
|
+
METHODS get_name
|
|
112
|
+
RETURNING VALUE(rv_name) TYPE string
|
|
113
|
+
ABSTRACT.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## FINAL Class Limitation
|
|
117
|
+
|
|
118
|
+
A FINAL class cannot have abstract methods. Use plain REDEFINITION instead:
|
|
119
|
+
|
|
120
|
+
```abap
|
|
121
|
+
" ❌ Wrong in FINAL class - syntax error
|
|
122
|
+
CLASS zcl_my_class DEFINITION PUBLIC FINAL.
|
|
123
|
+
METHODS parse_request ABSTRACT REDEFINITION.
|
|
124
|
+
ENDCLASS.
|
|
125
|
+
|
|
126
|
+
" ✅ Correct in FINAL class - use REDEFINITION only
|
|
127
|
+
CLASS zcl_my_class DEFINITION PUBLIC FINAL.
|
|
128
|
+
METHODS parse_request REDEFINITION.
|
|
129
|
+
ENDCLASS.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Working with TYPE any
|
|
133
|
+
|
|
134
|
+
TYPE any cannot be used with CREATE DATA. When a base class defines parameters with TYPE any, use a typed local variable in the subclass:
|
|
135
|
+
|
|
136
|
+
```abap
|
|
137
|
+
" Base class defines:
|
|
138
|
+
CLASS zcl_base DEFINITION PUBLIC ABSTRACT.
|
|
139
|
+
PROTECTED SECTION.
|
|
140
|
+
METHODS parse_request
|
|
141
|
+
IMPORTING iv_json TYPE string
|
|
142
|
+
EXPORTING es_request TYPE any.
|
|
143
|
+
ENDCLASS.
|
|
144
|
+
|
|
145
|
+
" Subclass implementation:
|
|
146
|
+
CLASS zcl_subclass DEFINITION PUBLIC FINAL.
|
|
147
|
+
INHERITING FROM zcl_base.
|
|
148
|
+
PROTECTED SECTION.
|
|
149
|
+
METHODS parse_request REDEFINITION.
|
|
150
|
+
ENDCLASS.
|
|
151
|
+
|
|
152
|
+
CLASS zcl_subclass IMPLEMENTATION.
|
|
153
|
+
METHOD parse_request.
|
|
154
|
+
" Use typed local variable
|
|
155
|
+
DATA: ls_request TYPE ty_my_params.
|
|
156
|
+
|
|
157
|
+
/ui2/cl_json=>deserialize(
|
|
158
|
+
EXPORTING json = iv_json
|
|
159
|
+
CHANGING data = ls_request ).
|
|
160
|
+
|
|
161
|
+
es_request = ls_request. " Assign typed to generic
|
|
162
|
+
ENDMETHOD.
|
|
163
|
+
ENDCLASS.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Key points:**
|
|
167
|
+
- Declare a local variable with the concrete type
|
|
168
|
+
- Deserialize JSON into the typed local variable
|
|
169
|
+
- Assign to the generic TYPE any parameter
|
|
@@ -1,110 +1,63 @@
|
|
|
1
|
-
# ABAP
|
|
1
|
+
# ABAP Object Naming Conventions
|
|
2
2
|
|
|
3
|
-
**Searchable keywords**: naming convention, Z prefix, namespace, object type, CLAS, INTF, PROG, TABL, DDLS
|
|
3
|
+
**Searchable keywords**: naming convention, Z prefix, namespace, object type, CLAS, INTF, PROG, TABL, DDLS
|
|
4
4
|
|
|
5
5
|
## TOPICS IN THIS FILE
|
|
6
|
-
1.
|
|
7
|
-
2.
|
|
8
|
-
3.
|
|
6
|
+
1. Naming Conventions
|
|
7
|
+
2. ABAP Object Types
|
|
8
|
+
3. XML Metadata (see guidelines/08_abapgit.md)
|
|
9
9
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
**CRITICAL CHECKLIST - Never Forget!**
|
|
13
|
-
|
|
14
|
-
When creating ANY new ABAP object file, you MUST also create its XML metadata file:
|
|
10
|
+
## Naming Conventions
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
12
|
+
Use `Z_` or `Y_` prefix for custom objects:
|
|
13
|
+
|
|
14
|
+
| Object Type | Prefix | Example |
|
|
15
|
+
|-------------|--------|---------|
|
|
16
|
+
| Class | ZCL_ | ZCL_MY_CLASS |
|
|
17
|
+
| Interface | ZIF_ | ZIF_MY_INTERFACE |
|
|
18
|
+
| Program | Z | ZMY_PROGRAM |
|
|
19
|
+
| Package | $ | $MY_PACKAGE |
|
|
20
|
+
| Table | Z | ZMY_TABLE |
|
|
21
|
+
| CDS View | ZC | ZC_MY_VIEW |
|
|
22
|
+
| CDS Entity | ZE | ZE_MY_ENTITY |
|
|
23
|
+
| Data Element | Z | ZMY_ELEMENT |
|
|
24
|
+
| Structure | Z | ZMY_STRUCTURE |
|
|
25
|
+
| Table Type | Z | ZMY_TABLE_TYPE |
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
## ABAP Object Types
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
Common object types in this project:
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
| Type | Description | File Suffix |
|
|
32
|
+
|------|-------------|-------------|
|
|
33
|
+
| CLAS | Classes | .clas.abap |
|
|
34
|
+
| PROG | Programs | .prog.abap |
|
|
35
|
+
| FUGR | Function Groups | .fugr.abap |
|
|
36
|
+
| INTF | Interfaces | .intf.abap |
|
|
37
|
+
| TABL | Tables | .tabl.abap |
|
|
38
|
+
| STRU | Structures | .stru.abap |
|
|
39
|
+
| DTEL | Data Elements | .dtel.abap |
|
|
40
|
+
| TTYP | Table Types | .ttyp.abap |
|
|
41
|
+
| DDLS | CDS Views | .ddls.asddls |
|
|
42
|
+
| DDLX | CDS Entities | .ddlx.asddlx |
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
29
|
-
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
30
|
-
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
31
|
-
<asx:values>
|
|
32
|
-
<VSEOCLASS>
|
|
33
|
-
<CLSNAME>ZCL_ABGAGT_UTIL</CLSNAME>
|
|
34
|
-
<LANGU>E</LANGU>
|
|
35
|
-
<DESCRIPT>Description</DESCRIPT>
|
|
36
|
-
<EXPOSURE>2</EXPOSURE>
|
|
37
|
-
<STATE>1</STATE>
|
|
38
|
-
<UNICODE>X</UNICODE>
|
|
39
|
-
</VSEOCLASS>
|
|
40
|
-
</asx:values>
|
|
41
|
-
</asx:abap>
|
|
42
|
-
</abapGit>
|
|
43
|
-
```
|
|
44
|
+
## XML Metadata
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
**See guidelines/08_abapgit.md for XML templates.**
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
Each ABAP object requires an XML metadata file for abapGit. Quick reference:
|
|
48
49
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<VSEOINTERF>
|
|
55
|
-
<CLSNAME>ZIF_ABGAGT_UTIL</CLSNAME>
|
|
56
|
-
<LANGU>E</LANGU>
|
|
57
|
-
<DESCRIPT>Description</DESCRIPT>
|
|
58
|
-
<EXPOSURE>2</EXPOSURE>
|
|
59
|
-
<STATE>1</STATE>
|
|
60
|
-
<UNICODE>X</UNICODE>
|
|
61
|
-
</VSEOINTERF>
|
|
62
|
-
</asx:values>
|
|
63
|
-
</asx:abap>
|
|
64
|
-
</abapGit>
|
|
50
|
+
```
|
|
51
|
+
Class: zcl_*.clas.xml
|
|
52
|
+
Interface: zif_*.intf.xml
|
|
53
|
+
Table: z*.tabl.xml
|
|
54
|
+
CDS View: zc_*.ddls.xml
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
1. **CRITICAL: Push to git BEFORE pulling into ABAP**
|
|
70
|
-
- Always commit and push ABAP files to git first
|
|
71
|
-
- Then run `abapgit-agent pull` to activate in ABAP
|
|
72
|
-
- Never run `abapgit-agent pull` without pushing changes first
|
|
73
|
-
|
|
74
|
-
2. **Only pull ABAP files** - The XML metadata stays in git:
|
|
75
|
-
```bash
|
|
76
|
-
abapgit-agent pull --files zcl_my_class.clas.abap
|
|
77
|
-
```
|
|
78
|
-
3. abapGit reads the XML from git to deserialize the ABAP code
|
|
79
|
-
4. XML files are NOT activated in ABAP - they are only for abapGit
|
|
80
|
-
|
|
57
|
+
**Important**: Always push to git BEFORE running pull:
|
|
81
58
|
```bash
|
|
82
|
-
# After making changes to ABAP files
|
|
83
59
|
git add .
|
|
84
|
-
git commit -m "
|
|
85
|
-
git push
|
|
86
|
-
|
|
87
|
-
# Then validate in ABAP system (single file - fast)
|
|
60
|
+
git commit -m "Changes"
|
|
61
|
+
git push # CRITICAL: Push FIRST
|
|
88
62
|
abapgit-agent pull --files abap/zcl_my_class.clas.abap
|
|
89
|
-
|
|
90
|
-
# Or validate all files
|
|
91
|
-
abapgit-agent pull
|
|
92
63
|
```
|
|
93
|
-
|
|
94
|
-
## Naming Conventions
|
|
95
|
-
|
|
96
|
-
- Use `Z_` or `Y_` prefix for custom objects
|
|
97
|
-
- Class names: `ZCL_<NAME>`
|
|
98
|
-
- Interface names: `ZIF_<NAME>`
|
|
99
|
-
- Programs: `Z<NAME>`
|
|
100
|
-
- Package: `$<PROJECT_NAME>`
|
|
101
|
-
|
|
102
|
-
## ABAP Object Types
|
|
103
|
-
|
|
104
|
-
Common object types in this project:
|
|
105
|
-
- `CLAS` - Classes
|
|
106
|
-
- `PROG` - Programs
|
|
107
|
-
- `FUGR` - Function Groups
|
|
108
|
-
- `INTF` - Interfaces
|
|
109
|
-
- `TABL` - Tables
|
|
110
|
-
- `DDLS` - Data Definitions
|
|
@@ -27,6 +27,7 @@ Key XML Settings:
|
|
|
27
27
|
Table DELIVERY: A=Application, C=Customizing
|
|
28
28
|
CDS SOURCE_TYPE: V=View, C=Consumption
|
|
29
29
|
Test Class XML: <WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
|
|
30
|
+
Local Classes: <CLSCCINCL>X</CLSCCINCL>
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
**Searchable keywords**: class xml, interface xml, table xml, cds xml, test class, exposure, serializer, abapgit
|
|
@@ -69,6 +70,16 @@ abapGit needs XML files to:
|
|
|
69
70
|
- `STATE`: State (1 = Active)
|
|
70
71
|
- `UNICODE`: Unicode encoding (X = Yes)
|
|
71
72
|
|
|
73
|
+
**Local Classes**: If the class has local classes (e.g., test doubles), add:
|
|
74
|
+
- `<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>` - for test classes
|
|
75
|
+
- `<CLSCCINCL>X</CLSCCINCL>` - for local class definitions
|
|
76
|
+
|
|
77
|
+
**Local Class Files**:
|
|
78
|
+
| File | Purpose |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| `zcl_xxx.clas.locals_def.abap` | Local class definitions |
|
|
81
|
+
| `zcl_xxx.clas.locals_imp.abap` | Local class implementations |
|
|
82
|
+
|
|
72
83
|
---
|
|
73
84
|
|
|
74
85
|
### Interface (INTF)
|
|
@@ -113,6 +113,27 @@ DATA mo_agent TYPE REF TO zcl_abgagt_agent. " Concrete class!
|
|
|
113
113
|
|
|
114
114
|
This allows you to replace the implementation with test doubles.
|
|
115
115
|
|
|
116
|
+
### Define Types in Interface
|
|
117
|
+
|
|
118
|
+
Define types needed by the interface directly in the interface to keep it self-contained:
|
|
119
|
+
|
|
120
|
+
```abap
|
|
121
|
+
INTERFACE zif_my_handler.
|
|
122
|
+
" Define types needed by the interface
|
|
123
|
+
TYPES: BEGIN OF ty_response,
|
|
124
|
+
success TYPE abap_bool,
|
|
125
|
+
message TYPE string,
|
|
126
|
+
END OF ty_response.
|
|
127
|
+
|
|
128
|
+
" Use the type in method signatures
|
|
129
|
+
METHODS process
|
|
130
|
+
IMPORTING iv_data TYPE string
|
|
131
|
+
RETURNING VALUE(rs_response) TYPE ty_response.
|
|
132
|
+
ENDINTERFACE.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This makes it easier for test doubles to implement the interface without needing separate type definitions.
|
|
136
|
+
|
|
116
137
|
### 3. Make Dependencies Injectable via Constructor
|
|
117
138
|
|
|
118
139
|
**Use constructor injection, not setter injection.**
|
package/bin/abapgit-agent
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* abapgit-agent init --folder <folder> --package <package> # Initialize: copies config, CLAUDE.md, guidelines
|
|
7
7
|
* abapgit-agent init --update # Update existing files to latest version
|
|
8
8
|
* abapgit-agent create
|
|
9
|
+
* abapgit-agent delete
|
|
9
10
|
* abapgit-agent import [--message <message>]
|
|
10
11
|
* abapgit-agent pull [--branch <branch>]
|
|
11
12
|
* abapgit-agent pull --url <git-url> [--branch <branch>]
|
|
@@ -1228,10 +1229,19 @@ async function runInit(args) {
|
|
|
1228
1229
|
const updateMode = args.includes('--update');
|
|
1229
1230
|
|
|
1230
1231
|
// Get parameters
|
|
1231
|
-
|
|
1232
|
+
let folder = folderArgIndex !== -1 && folderArgIndex + 1 < args.length
|
|
1232
1233
|
? args[folderArgIndex + 1]
|
|
1233
1234
|
: '/src/';
|
|
1234
1235
|
|
|
1236
|
+
// Normalize folder path: ensure it starts with / and ends with /
|
|
1237
|
+
folder = folder.trim();
|
|
1238
|
+
if (!folder.startsWith('/')) {
|
|
1239
|
+
folder = '/' + folder;
|
|
1240
|
+
}
|
|
1241
|
+
if (!folder.endsWith('/')) {
|
|
1242
|
+
folder = folder + '/';
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1235
1245
|
const packageName = packageArgIndex !== -1 && packageArgIndex + 1 < args.length
|
|
1236
1246
|
? args[packageArgIndex + 1]
|
|
1237
1247
|
: null;
|
|
@@ -1503,7 +1513,7 @@ To enable integration:
|
|
|
1503
1513
|
}
|
|
1504
1514
|
|
|
1505
1515
|
// Version compatibility check for commands that interact with ABAP
|
|
1506
|
-
const abapCommands = ['create', 'import', 'pull', 'inspect', 'unit', 'tree', 'view', 'preview', 'list'];
|
|
1516
|
+
const abapCommands = ['create', 'delete', 'import', 'pull', 'inspect', 'unit', 'tree', 'view', 'preview', 'list'];
|
|
1507
1517
|
if (command && abapCommands.includes(command)) {
|
|
1508
1518
|
await checkVersionCompatibility();
|
|
1509
1519
|
}
|
|
@@ -1605,6 +1615,67 @@ Examples:
|
|
|
1605
1615
|
break;
|
|
1606
1616
|
}
|
|
1607
1617
|
|
|
1618
|
+
case 'delete': {
|
|
1619
|
+
const helpIndex = args.findIndex(a => a === '--help' || a === '-h');
|
|
1620
|
+
|
|
1621
|
+
// Show help if requested
|
|
1622
|
+
if (helpIndex !== -1) {
|
|
1623
|
+
console.log(`
|
|
1624
|
+
Usage:
|
|
1625
|
+
abapgit-agent delete
|
|
1626
|
+
|
|
1627
|
+
Description:
|
|
1628
|
+
Delete abapGit online repository from ABAP system.
|
|
1629
|
+
Auto-detects URL from git remote of current directory.
|
|
1630
|
+
|
|
1631
|
+
Prerequisites:
|
|
1632
|
+
- Run "abapgit-agent create" first
|
|
1633
|
+
|
|
1634
|
+
Examples:
|
|
1635
|
+
abapgit-agent delete # Delete repo for current git remote
|
|
1636
|
+
`);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Get URL from current git remote
|
|
1641
|
+
const config = loadConfig();
|
|
1642
|
+
const repoUrl = getGitRemoteUrl();
|
|
1643
|
+
|
|
1644
|
+
if (!repoUrl) {
|
|
1645
|
+
console.error('Error: No git remote configured. Please configure a remote origin.');
|
|
1646
|
+
process.exit(1);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
console.log(`\n🗑️ Deleting online repository`);
|
|
1650
|
+
console.log(` URL: ${repoUrl}`);
|
|
1651
|
+
|
|
1652
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
1653
|
+
|
|
1654
|
+
const data = {
|
|
1655
|
+
url: repoUrl
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/delete', data, { csrfToken });
|
|
1659
|
+
|
|
1660
|
+
console.log('\n');
|
|
1661
|
+
|
|
1662
|
+
// Handle uppercase keys from ABAP
|
|
1663
|
+
const success = result.SUCCESS || result.success;
|
|
1664
|
+
const repoKey = result.REPO_KEY || result.repo_key;
|
|
1665
|
+
const message = result.MESSAGE || result.message;
|
|
1666
|
+
const error = result.ERROR || result.error;
|
|
1667
|
+
|
|
1668
|
+
if (success === 'X' || success === true) {
|
|
1669
|
+
console.log(`✅ Repository deleted successfully!`);
|
|
1670
|
+
console.log(` Key: ${repoKey}`);
|
|
1671
|
+
} else {
|
|
1672
|
+
console.log(`❌ Failed to delete repository`);
|
|
1673
|
+
console.log(` Error: ${error || message || 'Unknown error'}`);
|
|
1674
|
+
process.exit(1);
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1608
1679
|
case 'import': {
|
|
1609
1680
|
const helpIndex = args.findIndex(a => a === '--help' || a === '-h');
|
|
1610
1681
|
|
|
@@ -1739,6 +1810,34 @@ Examples:
|
|
|
1739
1810
|
if (isAbapIntegrationEnabled()) {
|
|
1740
1811
|
console.log('✅ ABAP Git Agent is ENABLED');
|
|
1741
1812
|
console.log(' Config location:', pathModule.join(process.cwd(), '.abapGitAgent'));
|
|
1813
|
+
|
|
1814
|
+
// Check if repo exists in ABAP
|
|
1815
|
+
const config = loadConfig();
|
|
1816
|
+
const repoUrl = getGitRemoteUrl();
|
|
1817
|
+
|
|
1818
|
+
if (repoUrl) {
|
|
1819
|
+
try {
|
|
1820
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
1821
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/status', { url: repoUrl }, { csrfToken });
|
|
1822
|
+
|
|
1823
|
+
const status = result.status || result.STATUS || result.SUCCESS;
|
|
1824
|
+
if (status === 'Found' || status === 'X' || status === true) {
|
|
1825
|
+
console.log(' Repository: ✅ Created');
|
|
1826
|
+
console.log(` Package: ${result.PACKAGE || result.package}`);
|
|
1827
|
+
console.log(` URL: ${repoUrl}`);
|
|
1828
|
+
console.log(` Key: ${result.REPO_KEY || result.repo_key}`);
|
|
1829
|
+
} else {
|
|
1830
|
+
console.log(' Repository: ❌ Not created in ABAP system');
|
|
1831
|
+
console.log(` URL: ${repoUrl}`);
|
|
1832
|
+
console.log(' Run "abapgit-agent create" to create repository');
|
|
1833
|
+
}
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
console.log(' Repository: ⚠️ Unable to check status');
|
|
1836
|
+
console.log(' Error:', err.message);
|
|
1837
|
+
}
|
|
1838
|
+
} else {
|
|
1839
|
+
console.log(' No git remote configured');
|
|
1840
|
+
}
|
|
1742
1841
|
} else {
|
|
1743
1842
|
console.log('❌ ABAP Git Agent is NOT configured');
|
|
1744
1843
|
}
|
|
@@ -2067,9 +2166,9 @@ Examples:
|
|
|
2067
2166
|
console.log(` ${description}`);
|
|
2068
2167
|
}
|
|
2069
2168
|
|
|
2070
|
-
// Display source code for classes, interfaces, and
|
|
2169
|
+
// Display source code for classes, interfaces, CDS views, and programs/source includes
|
|
2071
2170
|
const source = obj.SOURCE || obj.source || '';
|
|
2072
|
-
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
|
|
2171
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program')) {
|
|
2073
2172
|
console.log('');
|
|
2074
2173
|
// Replace escaped newlines with actual newlines and display
|
|
2075
2174
|
const displaySource = source.replace(/\\n/g, '\n');
|
|
@@ -2439,6 +2538,105 @@ Examples:
|
|
|
2439
2538
|
break;
|
|
2440
2539
|
}
|
|
2441
2540
|
|
|
2541
|
+
case 'where': {
|
|
2542
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
2543
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
2544
|
+
console.error('Error: --objects parameter required');
|
|
2545
|
+
console.error('Usage: abapgit-agent where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]');
|
|
2546
|
+
console.error('Example: abapgit-agent where --objects ZCL_MY_CLASS');
|
|
2547
|
+
console.error('Example: abapgit-agent where --objects ZIF_MY_INTERFACE');
|
|
2548
|
+
console.error('Example: abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20');
|
|
2549
|
+
process.exit(1);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
2553
|
+
const typeArg = args.indexOf('--type');
|
|
2554
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
2555
|
+
const limitArg = args.indexOf('--limit');
|
|
2556
|
+
const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 100;
|
|
2557
|
+
const jsonOutput = args.includes('--json');
|
|
2558
|
+
|
|
2559
|
+
console.log(`\n Where-used list for ${objects.length} object(s)`);
|
|
2560
|
+
|
|
2561
|
+
const config = loadConfig();
|
|
2562
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
2563
|
+
|
|
2564
|
+
const data = {
|
|
2565
|
+
objects: objects,
|
|
2566
|
+
limit: Math.min(Math.max(1, limit), 500)
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
if (type) {
|
|
2570
|
+
data.type = type;
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/where', data, { csrfToken });
|
|
2574
|
+
|
|
2575
|
+
// Handle uppercase keys from ABAP
|
|
2576
|
+
const success = result.SUCCESS || result.success;
|
|
2577
|
+
const whereObjects = result.OBJECTS || result.objects || [];
|
|
2578
|
+
const message = result.MESSAGE || result.message || '';
|
|
2579
|
+
const error = result.ERROR || result.error;
|
|
2580
|
+
|
|
2581
|
+
if (!success || error) {
|
|
2582
|
+
console.error(`\n Error: ${error || 'Failed to get where-used list'}`);
|
|
2583
|
+
break;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
if (jsonOutput) {
|
|
2587
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2588
|
+
} else {
|
|
2589
|
+
console.log(`\n ${message}`);
|
|
2590
|
+
console.log('');
|
|
2591
|
+
|
|
2592
|
+
for (let i = 0; i < whereObjects.length; i++) {
|
|
2593
|
+
const obj = whereObjects[i];
|
|
2594
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
2595
|
+
const objType = obj.TYPE || obj.type || '';
|
|
2596
|
+
const error = obj.ERROR || obj.error || '';
|
|
2597
|
+
const references = obj.REFERENCES || obj.references || [];
|
|
2598
|
+
const count = obj.COUNT || obj.count || 0;
|
|
2599
|
+
|
|
2600
|
+
// Handle object not found error
|
|
2601
|
+
if (error) {
|
|
2602
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2603
|
+
console.log(` ${error}`);
|
|
2604
|
+
console.log('');
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
if (count === 0) {
|
|
2609
|
+
console.log(` ❌ ${objName} (${objType})`);
|
|
2610
|
+
console.log(` No references found`);
|
|
2611
|
+
console.log('');
|
|
2612
|
+
continue;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
console.log(` 🔍 ${objName} (${objType})`);
|
|
2616
|
+
console.log(` Found ${count} reference(s):`);
|
|
2617
|
+
console.log('');
|
|
2618
|
+
|
|
2619
|
+
// Display references - one line format: include → method (type) or include (type)
|
|
2620
|
+
for (let j = 0; j < references.length; j++) {
|
|
2621
|
+
const ref = references[j];
|
|
2622
|
+
const includeName = ref.INCLUDE_NAME || ref.include_name || '';
|
|
2623
|
+
const includeType = ref.INCLUDE_TYPE || ref.include_type || '';
|
|
2624
|
+
const methodName = ref.METHOD_NAME || ref.method_name || '';
|
|
2625
|
+
|
|
2626
|
+
let line;
|
|
2627
|
+
if (methodName) {
|
|
2628
|
+
line = ` ${j + 1}. ${includeName} → ${methodName} (${includeType})`;
|
|
2629
|
+
} else {
|
|
2630
|
+
line = ` ${j + 1}. ${includeName} (${includeType})`;
|
|
2631
|
+
}
|
|
2632
|
+
console.log(line);
|
|
2633
|
+
}
|
|
2634
|
+
console.log('');
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
break;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2442
2640
|
case 'ref': {
|
|
2443
2641
|
const refSearch = require('../src/ref-search');
|
|
2444
2642
|
const topicIndex = args.indexOf('--topic');
|
|
@@ -2564,6 +2762,9 @@ Commands:
|
|
|
2564
2762
|
view --objects <obj1>,<obj2>,... [--type <type>] [--json]
|
|
2565
2763
|
View ABAP object definitions from the ABAP system
|
|
2566
2764
|
|
|
2765
|
+
where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]
|
|
2766
|
+
Find where-used list for ABAP objects (classes, interfaces, programs)
|
|
2767
|
+
|
|
2567
2768
|
ref <pattern> [--json]
|
|
2568
2769
|
Search ABAP reference repositories for patterns. Requires referenceFolder in .abapGitAgent.
|
|
2569
2770
|
|
|
@@ -2589,6 +2790,7 @@ Examples:
|
|
|
2589
2790
|
abapgit-agent init --folder /src --package ZMY_PACKAGE # Initialize
|
|
2590
2791
|
abapgit-agent init --update # Update files to latest
|
|
2591
2792
|
abapgit-agent create # Create repo
|
|
2793
|
+
abapgit-agent delete # Delete repo
|
|
2592
2794
|
abapgit-agent import # Import objects to git
|
|
2593
2795
|
abapgit-agent import --message "Initial import" # Import with message
|
|
2594
2796
|
abapgit-agent pull # Auto-detect from git
|
|
@@ -2610,6 +2812,10 @@ Examples:
|
|
|
2610
2812
|
abapgit-agent view --objects ZIF_MY_INTERFACE --type INTF # View interface
|
|
2611
2813
|
abapgit-agent view --objects ZMY_TABLE --type TABL # View table structure
|
|
2612
2814
|
abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2 --json # Multiple objects
|
|
2815
|
+
abapgit-agent where --objects ZCL_SUT_AUNIT_RUNNER # Find where class is used
|
|
2816
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE # Find interface implementations
|
|
2817
|
+
abapgit-agent where --objects CL_SUT_AUNIT_RUNNER --limit 20 # Limit results
|
|
2818
|
+
abapgit-agent where --objects ZIF_MY_INTERFACE --json # JSON output
|
|
2613
2819
|
abapgit-agent ref "CORRESPONDING" # Search all reference repos
|
|
2614
2820
|
abapgit-agent ref "CX_SY_" # Search exceptions
|
|
2615
2821
|
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.1",
|
|
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 --aunit",
|
|
22
|
+
"test:cmd": "node scripts/test-all.js --cmd",
|
|
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 = {
|