abapgit-agent 1.16.3 → 1.17.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/abap/CLAUDE.md +7 -0
- package/abap/CLAUDE.slim.md +4 -0
- package/abap/guidelines/abapgit.md +12 -1
- package/abap/guidelines/debug-session.md +21 -0
- package/abap/guidelines/enho.md +193 -0
- package/abap/guidelines/index.md +7 -1
- package/abap/guidelines/object-creation.md +1 -0
- package/abap/guidelines/workflow-detailed.md +23 -21
- package/package.json +2 -1
- package/src/commands/customize.js +157 -0
- package/src/commands/drop.js +1 -1
- package/src/commands/help.js +4 -0
- package/src/commands/index.js +2 -1
- package/src/commands/pull.js +3 -2
- package/src/commands/syntax.js +4 -1
- package/src/commands/transport.js +12 -2
- package/src/commands/view.js +55 -2
- package/src/utils/transport-selector.js +37 -15
package/abap/CLAUDE.md
CHANGED
|
@@ -123,6 +123,7 @@ Use the object name from `objects.local.md` (or `objects.md` as fallback) in pla
|
|
|
123
123
|
| CDS View (DDLS) | `<name>.ddls.asddls` | `<name>.ddls.xml` |
|
|
124
124
|
| CDS Access Control (DCLS) | `<name>.dcls.asdcls` | `<name>.dcls.xml` |
|
|
125
125
|
| Function Group (FUGR) | `<name>.fugr.abap` + includes | `<name>.fugr.xml` |
|
|
126
|
+
| Enhancement (ENHO) | `<name>.enho.<hash>.abap` (one per hook) | `<name>.enho.xml` |
|
|
126
127
|
| Table (TABL) | *(none)* | `<name>.tabl.xml` |
|
|
127
128
|
| Structure (STRU) | *(none)* | `<name>.tabl.xml` ⚠️ NOT `.stru.xml` |
|
|
128
129
|
| Data Element (DTEL) | *(none)* | `<name>.dtel.xml` |
|
|
@@ -786,6 +787,11 @@ Modified ABAP files?
|
|
|
786
787
|
├─ DCLS (CDS access control)?
|
|
787
788
|
│ └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --files abap/zc_view.dcls.xml --sync-xml
|
|
788
789
|
│ ⚠️ Pass the .xml file — pull --files does NOT accept .asdcls extensions
|
|
790
|
+
├─ ENHO (Enhancement)?
|
|
791
|
+
│ └─ ✅ Use: syntax (optional) → [abaplint] → commit → push → pull --files abap/<name>.enho.xml --sync-xml
|
|
792
|
+
│ ⚠️ syntax checks basic errors in the hook body; semantic checks require pull
|
|
793
|
+
│ ⚠️ Pass the .enho.xml file to pull — hash .abap files also work but xml is preferred
|
|
794
|
+
│ → see: abapgit-agent ref --topic enho
|
|
789
795
|
└─ Other complex objects?
|
|
790
796
|
└─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --sync-xml → (if errors: inspect)
|
|
791
797
|
|
|
@@ -818,6 +824,7 @@ Modified ABAP files?
|
|
|
818
824
|
| `ref --topic abapgit` | abapGit XML Metadata — **use for CDS/DDLS XML**, also CLAS, INTF, PROG, DCLS, FUGR |
|
|
819
825
|
| `ref --topic abapgit-xml-only` | abapGit XML Metadata — XML-only objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG) |
|
|
820
826
|
| `ref --topic abapgit-fugr` | abapGit XML Metadata — Function Group (FUGR) details |
|
|
827
|
+
| `ref --topic enho` | Enhancement Objects (ENHO) — workflow, hash algorithm, creation guide |
|
|
821
828
|
| `ref --topic abaplint` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
|
|
822
829
|
| `ref --topic debug-session` | Debug Session Guide |
|
|
823
830
|
| `ref --topic debug-dump` | Dump Analysis Guide |
|
package/abap/CLAUDE.slim.md
CHANGED
|
@@ -42,3 +42,7 @@ The `ref` command uses bundled guidelines automatically — no local `guidelines
|
|
|
42
42
|
## Project-Specific Naming Conventions
|
|
43
43
|
|
|
44
44
|
See `guidelines/objects.local.md` for this project's naming convention overrides.
|
|
45
|
+
|
|
46
|
+
## CLI Commands
|
|
47
|
+
|
|
48
|
+
Run `abapgit-agent --help` to see all available commands (pull, customize, transport, preview, and more).
|
|
@@ -22,7 +22,8 @@ Interface | zif_*.intf.abap | zif_*.intf.xml
|
|
|
22
22
|
Program | z*.prog.abap | z*.prog.xml
|
|
23
23
|
CDS View (DDLS) | zc_*.ddls.asddls | zc_*.ddls.xml
|
|
24
24
|
CDS Access Control | zc_*.dcls.asdcls | zc_*.dcls.xml
|
|
25
|
-
Function Group | z*fugr*.abap (
|
|
25
|
+
Function Group | z*fugr*.abap (1 per FM + includes) | z*.fugr.xml
|
|
26
|
+
Enhancement (ENHO) | z*.enho.<hash>.abap (N files)| z*.enho.xml
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
> **XML-only objects** (TABL, STRU, DTEL, TTYP, DOMA, MSAG) have **no ABAP source file** and are not covered here.
|
|
@@ -302,6 +303,16 @@ FUGR serializes as multiple files (main XML + TOP include + SAPL include + one s
|
|
|
302
303
|
|
|
303
304
|
---
|
|
304
305
|
|
|
306
|
+
### Enhancement (ENHO)
|
|
307
|
+
|
|
308
|
+
**Files**: `src/<name>.enho.xml` + `src/<name>.enho.<hash>.abap` (one per hook)
|
|
309
|
+
|
|
310
|
+
ENHO is a rare, specialized object type. For the full XML template, hash algorithm, and workflow:
|
|
311
|
+
|
|
312
|
+
→ `abapgit-agent ref --topic enho`
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
305
316
|
## Important Notes
|
|
306
317
|
|
|
307
318
|
1. **ALWAYS push to git BEFORE running pull** - abapGit reads from git
|
|
@@ -42,6 +42,27 @@ The output varies by section type:
|
|
|
42
42
|
|
|
43
43
|
The hint already points to the first **executable** line, skipping `METHOD`, blank lines, comments, and all declaration forms (`DATA`, `DATA:`, `DATA(`).
|
|
44
44
|
|
|
45
|
+
**Classes with active ENHO hook enhancements** — `view --full --lines` injects the ENHO hook lines into the CM section so you can see the runtime code layout. The `G` numbers for injected lines are shown for reference, but the BP hint uses **base-source coordinates** (subtracting the injected ENHO lines) because ADT validates breakpoints against the non-injected `/source/main`:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
* ---- Method: COMPUTE (CM001) — breakpoint: debug set --objects ZCL_CAIS_DBG_TRIGGER:33 ----
|
|
49
|
+
29 [ 1] METHOD compute.
|
|
50
|
+
30 [ 2] *"* ENHO: ZCAIS_DBG_ENHO (BEGIN)
|
|
51
|
+
31 [ 3] DATA lv_enho_marker TYPE i.
|
|
52
|
+
32 [ 4] lv_enho_marker = iv_a * iv_b.
|
|
53
|
+
33 [ 5] *"* ENHO END
|
|
54
|
+
34 [ 6] " compute: add two integers...
|
|
55
|
+
35 [ 7] DATA lv_sum TYPE i.
|
|
56
|
+
36 [ 8]
|
|
57
|
+
37 [ 9] lv_sum = iv_a + iv_b. ← lives at G=37 in runtime layout
|
|
58
|
+
38 [ 10] rv_result = lv_sum.
|
|
59
|
+
39 [ 11] ENDMETHOD.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The first executable original line is `lv_sum = iv_a + iv_b.` at runtime G=37, but its position in ADT's base source (without the 4 injected ENHO lines) is G=33 (37 − 4). The hint says `:33`, not `:37`. ADT accepts `:33` and the breakpoint fires correctly inside the ENHO-affected method.
|
|
63
|
+
|
|
64
|
+
> **ENHO body lines (G 30–32 in the example) cannot be used as breakpoints.** Setting a BP at those G numbers returns "Not registered on server" — ADT's base source has no ENHO lines. The BP hint already points past the ENHO block to the first original executable line.
|
|
65
|
+
|
|
45
66
|
**Two scenarios for CM* methods:**
|
|
46
67
|
|
|
47
68
|
| Scenario | Command |
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Enhancement Objects (ENHO)
|
|
4
|
+
nav_order: 15
|
|
5
|
+
parent: ABAP Coding Guidelines
|
|
6
|
+
grand_parent: ABAP Development
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Enhancement Objects (ENHO) — Workflow Guide
|
|
10
|
+
|
|
11
|
+
ENHO (Hook Enhancements) let you inject ABAP code into specific spots of existing methods without modifying the original class. Use them sparingly — only when you cannot subclass or use dependency injection.
|
|
12
|
+
|
|
13
|
+
**Searchable keywords**: enho, enhancement, hook, hook implementation, enhancements, hook_impl
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Creating Brand-New ENHOs
|
|
18
|
+
|
|
19
|
+
**abapGit pull can create brand-new ENHOs headlessly** — write the XML and ABAP files, push, and pull as normal.
|
|
20
|
+
|
|
21
|
+
This works because abapGit's ENHO hook deserializer passes `dark = abap_true` to `cl_enh_factory=>create_enhancement`, which suppresses the Modification Assistant dialog (`SAPLSTRD 0353`) that would otherwise require a GUI session. The `save()` call also uses `run_dark = abap_true`.
|
|
22
|
+
|
|
23
|
+
### Workflow for new ENHOs
|
|
24
|
+
|
|
25
|
+
1. Write `<name>.enho.xml` (see XML Template section below)
|
|
26
|
+
2. For each hook: compute `hash = SHA1(full_name)[0:8]` → write `<name>.enho.<hash>.abap`
|
|
27
|
+
3. Commit, push, then pull:
|
|
28
|
+
```bash
|
|
29
|
+
abapgit-agent pull --files src/<name>.enho.xml --sync-xml
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `--sync-xml` flag is important for new objects — abapGit's serializer may rewrite the XML during the first pull.
|
|
33
|
+
|
|
34
|
+
Once the ENHO exists in the system, abapGit's full-replace deserialization (delete + recreate) works correctly via REST.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## File Naming Pattern
|
|
39
|
+
|
|
40
|
+
An ENHO object produces **multiple** files:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
<name>.enho.xml ← object metadata (describes all hooks)
|
|
44
|
+
<name>.enho.<hash>.abap ← hook source code (one file per hook)
|
|
45
|
+
<name>.enho.<hash2>.abap ← second hook source (if multiple hooks)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Hash Computation
|
|
49
|
+
|
|
50
|
+
The `<hash>` is the first 8 hex characters of SHA1(`full_name`):
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
// Node.js
|
|
54
|
+
const crypto = require('crypto');
|
|
55
|
+
const fullName = '\\TY:ZCL_TARGET_CLASS\\ME:GET_VALUE\\SE:BEGIN\\EI';
|
|
56
|
+
const hash = crypto.createHash('sha1').update(fullName).digest('hex').substring(0, 8);
|
|
57
|
+
// → 'd639f45c'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `full_name` Format
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
\TY:<CLASS>\ME:<METHOD>\SE:<SECTION>\EI
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Part | Value | Meaning |
|
|
67
|
+
|------|-------|---------|
|
|
68
|
+
| `\TY:` | class name (uppercase) | Target class |
|
|
69
|
+
| `\IN:` | interface name (uppercase) | Interface (only when method is from an interface) |
|
|
70
|
+
| `\ME:` | method name (uppercase) | Target method |
|
|
71
|
+
| `\SE:` | `BEGIN` or `END` | Hook point |
|
|
72
|
+
| `\EI` | *(fixed suffix)* | End of identifier |
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
```
|
|
76
|
+
\TY:ZCL_TARGET\ME:GET_VALUE\SE:BEGIN\EI
|
|
77
|
+
\TY:CL_MPC_EXT\IN:/IWBEP/IF_MGW_APPL_SRV_RUNTIME\ME:GET_ENTITYSET\SE:BEGIN\EI
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The `full_name` and its hash are stored in the XML `<FILES>/<item>/<FILE>` element — read them directly from the `.enho.xml` file when the ENHO already exists in git.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Pull Behavior
|
|
85
|
+
|
|
86
|
+
ENHO deserialization is **full replace**: abapGit deletes the entire ENHO object and recreates all its hooks from git. This is the same as for classes and interfaces.
|
|
87
|
+
|
|
88
|
+
- Passing the `.enho.xml` file targets the whole ENHO (preferred)
|
|
89
|
+
- Passing any `.enho.<hash>.abap` file also targets the whole ENHO (resolves to same object)
|
|
90
|
+
- **Other ENHO objects are NOT affected** — the object filter is at the ENHO name level
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Both commands activate the whole ZFOO_ENH (all hooks)
|
|
94
|
+
abapgit-agent pull --files src/zfoo_enh.enho.xml
|
|
95
|
+
abapgit-agent pull --files src/zfoo_enh.enho.d639f45c.abap
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Workflow — Editing an Existing ENHO
|
|
101
|
+
|
|
102
|
+
When an ENHO already exists in both git and the ABAP system:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Edit the hook source
|
|
106
|
+
vi src/zfoo_enh.enho.d639f45c.abap
|
|
107
|
+
|
|
108
|
+
# Syntax check (optional — catches basic errors before commit)
|
|
109
|
+
abapgit-agent syntax --files src/zfoo_enh.enho.d639f45c.abap
|
|
110
|
+
|
|
111
|
+
# Commit, push, pull
|
|
112
|
+
git add src/zfoo_enh.enho.d639f45c.abap
|
|
113
|
+
git commit -m "fix: update enhancement hook"
|
|
114
|
+
git push
|
|
115
|
+
abapgit-agent pull --files src/zfoo_enh.enho.xml --sync-xml
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Note on Syntax Command
|
|
121
|
+
|
|
122
|
+
The `syntax` command supports ENHO `.enho.<hash>.abap` files. It extracts the code between `ENHANCEMENT`/`ENDENHANCEMENT`, wraps it in a minimal program skeleton, and runs `SYNTAX-CHECK` with the target class pool's uccheck/unicode settings.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
abapgit-agent syntax --files src/zfoo_enh.enho.d639f45c.abap
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Limitation**: Because the code is checked outside the real class pool context, instance variables and class-specific types from the target class are not visible. The check catches basic syntax errors (undefined keywords, missing periods, etc.) but not semantic errors like unknown field names from the enhanced class.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## XML Template
|
|
133
|
+
|
|
134
|
+
This is the format abapGit actually serializes. Use a real abapGit export as the source of truth — copy from an existing ENHO in your project.
|
|
135
|
+
|
|
136
|
+
```xml
|
|
137
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
138
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_ENHO" serializer_version="v1.0.0">
|
|
139
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
140
|
+
<asx:values>
|
|
141
|
+
<TOOL>HOOK_IMPL</TOOL>
|
|
142
|
+
<SHORTTEXT>My enhancement description</SHORTTEXT>
|
|
143
|
+
<ORIGINAL_OBJECT>
|
|
144
|
+
<PGMID>R3TR</PGMID>
|
|
145
|
+
<ORG_OBJ_TYPE>CLAS</ORG_OBJ_TYPE>
|
|
146
|
+
<ORG_OBJ_NAME>ZCL_TARGET_CLASS</ORG_OBJ_NAME>
|
|
147
|
+
<ORG_MAIN_TYPE>CLAS</ORG_MAIN_TYPE>
|
|
148
|
+
<ORG_MAIN_NAME>ZCL_TARGET_CLASS</ORG_MAIN_NAME>
|
|
149
|
+
<PROGRAMNAME>ZCL_TARGET_CLASS================CP</PROGRAMNAME>
|
|
150
|
+
</ORIGINAL_OBJECT>
|
|
151
|
+
<ENHANCEMENTS>
|
|
152
|
+
<ENH_HOOK_IMPL>
|
|
153
|
+
<PROGRAMNAME>ZCL_TARGET_CLASS================CP</PROGRAMNAME>
|
|
154
|
+
<ENHMODE>D</ENHMODE>
|
|
155
|
+
<FULL_NAME>\TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI</FULL_NAME>
|
|
156
|
+
</ENH_HOOK_IMPL>
|
|
157
|
+
</ENHANCEMENTS>
|
|
158
|
+
<FILES>
|
|
159
|
+
<item>
|
|
160
|
+
<NAME>\TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI</NAME>
|
|
161
|
+
<FILE>d639f45c</FILE>
|
|
162
|
+
</item>
|
|
163
|
+
</FILES>
|
|
164
|
+
</asx:values>
|
|
165
|
+
</asx:abap>
|
|
166
|
+
</abapGit>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Key elements:
|
|
170
|
+
- `<TOOL>HOOK_IMPL</TOOL>` — always `HOOK_IMPL` for hook enhancements
|
|
171
|
+
- `<SHORTTEXT>` — short description text
|
|
172
|
+
- `<ORIGINAL_OBJECT>` — the class being enhanced
|
|
173
|
+
- `<PROGRAMNAME>` — class pool name: classname padded to 30 chars with `=`, then `CP` (total 32 chars)
|
|
174
|
+
- Example: `ZCL_MY_CLASS` (12 chars) → `ZCL_MY_CLASS==================CP`
|
|
175
|
+
- `<ENHANCEMENTS>/<ENH_HOOK_IMPL>/<FULL_NAME>` — the full_name identifier
|
|
176
|
+
- `<ENHMODE>D` — Draft mode (standard value)
|
|
177
|
+
- `<FILES>/<item>/<NAME>` — same full_name as in ENHANCEMENTS
|
|
178
|
+
- `<FILES>/<item>/<FILE>` — first 8 hex chars of SHA1(full_name)
|
|
179
|
+
|
|
180
|
+
For multiple hooks: add additional `<ENH_HOOK_IMPL>` blocks in `<ENHANCEMENTS>` and additional `<item>` blocks in `<FILES>`.
|
|
181
|
+
|
|
182
|
+
### Hash file format (`.enho.<hash>.abap`)
|
|
183
|
+
|
|
184
|
+
```abap
|
|
185
|
+
"Name: \TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI
|
|
186
|
+
ENHANCEMENT 0 ZFOO_ENH.
|
|
187
|
+
" Your ABAP code goes here
|
|
188
|
+
DATA lv_result TYPE string.
|
|
189
|
+
lv_result = 'modified'.
|
|
190
|
+
ENDENHANCEMENT.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The first line is a comment with the `full_name`. `ENHANCEMENT 0 <ENHNAME>.` and `ENDENHANCEMENT.` are the wrapper statements — the actual code goes between them.
|
package/abap/guidelines/index.md
CHANGED
|
@@ -22,7 +22,11 @@ This folder contains detailed ABAP coding guidelines that can be searched using
|
|
|
22
22
|
| `objects.md` | Object Naming Conventions |
|
|
23
23
|
| `naming-limits.md` | Naming Length Limits (30/16/40 char rules per type) |
|
|
24
24
|
| `json.md` | JSON Handling |
|
|
25
|
-
| `
|
|
25
|
+
| `string-template.md` | String Templates — syntax, escaping `\{` `\}`, JSON payloads |
|
|
26
|
+
| `abapgit.md` | abapGit XML Metadata — CLAS, INTF, PROG, CDS/DDLS, DCLS, FUGR |
|
|
27
|
+
| `abapgit-xml-only.md` | abapGit XML Metadata — XML-only objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG) |
|
|
28
|
+
| `abapgit-fugr.md` | abapGit XML Metadata — Function Group (FUGR) details |
|
|
29
|
+
| `enho.md` | Enhancement Objects (ENHO) — workflow, hash algorithm, creation guide |
|
|
26
30
|
| `unit-testable-code.md` | Unit Testable Code Guidelines (Dependency Injection) |
|
|
27
31
|
| `common-errors.md` | Common ABAP Errors - Quick Fixes |
|
|
28
32
|
| `debug-session.md` | Debug Session Guide |
|
|
@@ -33,6 +37,8 @@ This folder contains detailed ABAP coding guidelines that can be searched using
|
|
|
33
37
|
| `cds-testing.md` | CDS Testing (Test Double Framework) |
|
|
34
38
|
| `abaplint.md` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
|
|
35
39
|
| `comments.md` | Documentation Comments (ABAP DOC, shorttext, @parameter, CDS //) |
|
|
40
|
+
| `run-probe-classes.md` | run Command — AI Guidelines (probe classes, scratchWorkspace) |
|
|
41
|
+
| `probe-poc.md` | Probe and PoC — Full Decision Flow |
|
|
36
42
|
|
|
37
43
|
## Usage
|
|
38
44
|
|
|
@@ -23,6 +23,7 @@ Replace `<name>` with the actual object name from this project's naming conventi
|
|
|
23
23
|
| CDS View Entity (DDLS) | `<name>.ddls.asddls` | `<name>.ddls.xml` | **Use by default** — `ref --topic abapgit` |
|
|
24
24
|
| CDS Access Control (DCLS) | `<name>.dcls.asdcls` | `<name>.dcls.xml` | `ref --topic abapgit` |
|
|
25
25
|
| Function Group (FUGR) | `<name>.fugr.abap` + includes | `<name>.fugr.xml` | `ref --topic abapgit` |
|
|
26
|
+
| Enhancement (ENHO) | `<name>.enho.<hash>.abap` (one per hook) | `<name>.enho.xml` | `ref --topic enho` |
|
|
26
27
|
| Table (TABL) | *(none — XML-only)* | `<name>.tabl.xml` | `ref --topic abapgit-xml-only` |
|
|
27
28
|
| Structure (STRU) | *(none — XML-only)* | `<name>.tabl.xml` ⚠️ NOT `.stru.xml` | `ref --topic abapgit-xml-only` |
|
|
28
29
|
| Data Element (DTEL) | *(none — XML-only)* | `<name>.dtel.xml` | `ref --topic abapgit-xml-only` |
|
|
@@ -18,9 +18,9 @@ grand_parent: ABAP Development
|
|
|
18
18
|
3. Write code → place in correct folder (e.g., src/zcl_*.clas.abap)
|
|
19
19
|
│
|
|
20
20
|
▼
|
|
21
|
-
4. Syntax check (for CLAS, INTF, PROG, DDLS only)
|
|
21
|
+
4. Syntax check (for CLAS, INTF, PROG, DDLS, ENHO only)
|
|
22
22
|
│
|
|
23
|
-
├─► CLAS/INTF/PROG/DDLS → abapgit-agent syntax --files <file>
|
|
23
|
+
├─► CLAS/INTF/PROG/DDLS/ENHO → abapgit-agent syntax --files <file>
|
|
24
24
|
│ │
|
|
25
25
|
│ ├─► Errors? → Fix locally (no commit needed), re-run syntax
|
|
26
26
|
│ │
|
|
@@ -31,7 +31,7 @@ grand_parent: ABAP Development
|
|
|
31
31
|
▼
|
|
32
32
|
4b. abaplint (OPTIONAL — only if .abaplint.json exists in repo root)
|
|
33
33
|
│
|
|
34
|
-
├─► .abaplint.json exists →
|
|
34
|
+
├─► .abaplint.json exists → abapgit-agent lint
|
|
35
35
|
│ │
|
|
36
36
|
│ ├─► Issues? → Fix locally, re-run abaplint
|
|
37
37
|
│ │ ⚠️ Before applying any quickfix: run abapgit-agent ref --topic abaplint
|
|
@@ -45,7 +45,7 @@ grand_parent: ABAP Development
|
|
|
45
45
|
5. Commit and push → git add . && git commit && git push
|
|
46
46
|
│
|
|
47
47
|
▼
|
|
48
|
-
6. Activate → abapgit-agent pull --files src/file.clas.abap
|
|
48
|
+
6. Activate → abapgit-agent pull --files src/file.clas.abap --sync-xml
|
|
49
49
|
│ (behaviour depends on .abapgit-agent.json — see AI Tool Guidelines)
|
|
50
50
|
│
|
|
51
51
|
▼
|
|
@@ -67,6 +67,7 @@ grand_parent: ABAP Development
|
|
|
67
67
|
| INTF (interfaces) | ✅ Supported | Run `syntax` before commit |
|
|
68
68
|
| PROG (programs) | ✅ Supported | Run `syntax` before commit |
|
|
69
69
|
| DDLS (CDS views) | ✅ Supported | Run `syntax` before commit (requires annotations) |
|
|
70
|
+
| ENHO (enhancements) | ✅ Supported | Run `syntax` before commit (basic errors only — no target class context) |
|
|
70
71
|
| FUGR (function groups) | ❌ Not supported | Skip syntax, use `pull` then `inspect` |
|
|
71
72
|
| TABL/DTEL/DOMA/MSAG/SHLP | ❌ Not supported | Skip syntax, just `pull` |
|
|
72
73
|
| All other types | ❌ Not supported | Skip syntax, just `pull` |
|
|
@@ -90,7 +91,7 @@ abapgit-agent syntax --files src/zif_my_interface.intf.abap # ✅ Works (no dep
|
|
|
90
91
|
git add src/zif_my_interface.intf.abap src/zif_my_interface.intf.xml
|
|
91
92
|
git commit -m "feat: add interface"
|
|
92
93
|
git push
|
|
93
|
-
abapgit-agent pull --files src/zif_my_interface.intf.abap # Interface now activated
|
|
94
|
+
abapgit-agent pull --files src/zif_my_interface.intf.abap --sync-xml # Interface now activated
|
|
94
95
|
|
|
95
96
|
# Step 2: Create class, syntax check, commit, activate
|
|
96
97
|
vim src/zcl_my_class.clas.abap
|
|
@@ -98,7 +99,7 @@ abapgit-agent syntax --files src/zcl_my_class.clas.abap # ✅ Works (interfa
|
|
|
98
99
|
git add src/zcl_my_class.clas.abap src/zcl_my_class.clas.xml
|
|
99
100
|
git commit -m "feat: add class implementing interface"
|
|
100
101
|
git push
|
|
101
|
-
abapgit-agent pull --files src/zcl_my_class.clas.abap
|
|
102
|
+
abapgit-agent pull --files src/zcl_my_class.clas.abap --sync-xml
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
**Benefits:**
|
|
@@ -123,7 +124,7 @@ git commit -m "feat: add interface and implementing class"
|
|
|
123
124
|
git push
|
|
124
125
|
|
|
125
126
|
# Pull both together
|
|
126
|
-
abapgit-agent pull --files src/zif_my_interface.intf.abap,src/zcl_my_class.clas.abap
|
|
127
|
+
abapgit-agent pull --files src/zif_my_interface.intf.abap,src/zcl_my_class.clas.abap --sync-xml
|
|
127
128
|
|
|
128
129
|
# Use inspect if errors occur
|
|
129
130
|
abapgit-agent inspect --files src/zcl_my_class.clas.abap
|
|
@@ -150,7 +151,7 @@ git commit -m "feat: add class and CDS view"
|
|
|
150
151
|
git push
|
|
151
152
|
|
|
152
153
|
# Pull all files together
|
|
153
|
-
abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
|
|
154
|
+
abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls --sync-xml
|
|
154
155
|
```
|
|
155
156
|
|
|
156
157
|
**When to use syntax vs abaplint vs inspect vs view**:
|
|
@@ -169,6 +170,7 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
|
|
|
169
170
|
├─ .intf.abap → INTF ✅ syntax supported
|
|
170
171
|
├─ .prog.abap → PROG ✅ syntax supported
|
|
171
172
|
├─ .ddls.asddls → DDLS ✅ syntax supported (requires proper annotations)
|
|
173
|
+
├─ .enho.<hash>.abap → ENHO ✅ syntax supported (basic errors; no target class context)
|
|
172
174
|
└─ All other extensions → ❌ syntax not supported
|
|
173
175
|
|
|
174
176
|
2. Check for dependencies:
|
|
@@ -177,25 +179,25 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
|
|
|
177
179
|
├─ CDS view uses table? → INDEPENDENT (table already exists)
|
|
178
180
|
└─ Unrelated bug fixes across files? → INDEPENDENT
|
|
179
181
|
|
|
180
|
-
3. For SUPPORTED types (CLAS/INTF/PROG/DDLS):
|
|
181
|
-
├─ INDEPENDENT files → Run syntax → [abaplint if enabled] → Fix errors → Commit → Push → Pull
|
|
182
|
+
3. For SUPPORTED types (CLAS/INTF/PROG/DDLS/ENHO):
|
|
183
|
+
├─ INDEPENDENT files → Run syntax → [abaplint if enabled] → Fix errors → Commit → Push → Pull --sync-xml
|
|
182
184
|
│
|
|
183
185
|
└─ DEPENDENT files (NEW objects):
|
|
184
186
|
├─ RECOMMENDED: Create underlying object first (interface, base class, etc.)
|
|
185
|
-
│ 1. Create underlying object → Syntax → [abaplint] → Commit → Push → Pull
|
|
186
|
-
│ 2. Create dependent object → Syntax (works!) → [abaplint] → Commit → Push → Pull
|
|
187
|
+
│ 1. Create underlying object → Syntax → [abaplint] → Commit → Push → Pull --sync-xml
|
|
188
|
+
│ 2. Create dependent object → Syntax (works!) → [abaplint] → Commit → Push → Pull --sync-xml
|
|
187
189
|
│ ✅ Benefits: Both syntax checks work, cleaner workflow
|
|
188
190
|
│
|
|
189
191
|
└─ ALTERNATIVE: If interface design uncertain, commit both together
|
|
190
|
-
→ Skip syntax → [abaplint] → Commit both → Push → Pull → (if errors: inspect)
|
|
192
|
+
→ Skip syntax → [abaplint] → Commit both → Push → Pull --sync-xml → (if errors: inspect)
|
|
191
193
|
|
|
192
194
|
4. For UNSUPPORTED types (FUGR, TABL, etc.):
|
|
193
|
-
Write code → Skip syntax → [abaplint] → Commit → Push → Pull → (if errors: inspect)
|
|
195
|
+
Write code → Skip syntax → [abaplint] → Commit → Push → Pull --sync-xml → (if errors: inspect)
|
|
194
196
|
|
|
195
197
|
5. For MIXED types (some supported + some unsupported):
|
|
196
|
-
Write all code → Run syntax on independent supported files ONLY → [abaplint] → Commit ALL → Push → Pull ALL
|
|
198
|
+
Write all code → Run syntax on independent supported files ONLY → [abaplint] → Commit ALL → Push → Pull ALL --sync-xml
|
|
197
199
|
|
|
198
|
-
[abaplint] = run
|
|
200
|
+
[abaplint] = run abapgit-agent lint only if .abaplint.json exists in repo root
|
|
199
201
|
before applying any quickfix: run abapgit-agent ref --topic abaplint
|
|
200
202
|
```
|
|
201
203
|
|
|
@@ -207,13 +209,13 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
|
|
|
207
209
|
vim src/zif_calculator.intf.abap
|
|
208
210
|
abapgit-agent syntax --files src/zif_calculator.intf.abap # ✅ Works
|
|
209
211
|
git commit -am "feat: add calculator interface" && git push
|
|
210
|
-
abapgit-agent pull --files src/zif_calculator.intf.abap # Interface activated
|
|
212
|
+
abapgit-agent pull --files src/zif_calculator.intf.abap --sync-xml # Interface activated
|
|
211
213
|
|
|
212
214
|
# Step 2: Class next
|
|
213
215
|
vim src/zcl_calculator.clas.abap
|
|
214
216
|
abapgit-agent syntax --files src/zcl_calculator.clas.abap # ✅ Works (interface exists!)
|
|
215
217
|
git commit -am "feat: implement calculator" && git push
|
|
216
|
-
abapgit-agent pull --files src/zcl_calculator.clas.abap
|
|
218
|
+
abapgit-agent pull --files src/zcl_calculator.clas.abap --sync-xml
|
|
217
219
|
```
|
|
218
220
|
|
|
219
221
|
**Scenario 2: Multiple independent classes**
|
|
@@ -222,7 +224,7 @@ abapgit-agent pull --files src/zcl_calculator.clas.abap
|
|
|
222
224
|
vim src/zcl_class1.clas.abap src/zcl_class2.clas.abap
|
|
223
225
|
abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
|
|
224
226
|
git commit -am "feat: add utility classes" && git push
|
|
225
|
-
abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
|
|
227
|
+
abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap --sync-xml
|
|
226
228
|
```
|
|
227
229
|
|
|
228
230
|
**Error indicators after pull:**
|
|
@@ -240,10 +242,10 @@ abapgit-agent syntax --files src/zc_my_view.ddls.asddls
|
|
|
240
242
|
abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zif_intf1.intf.abap,src/zc_view.ddls.asddls
|
|
241
243
|
|
|
242
244
|
# 2. Pull/activate AFTER pushing to git
|
|
243
|
-
abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
|
|
245
|
+
abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap --sync-xml
|
|
244
246
|
|
|
245
247
|
# Override conflict detection for a single pull (e.g. deliberate branch switch)
|
|
246
|
-
abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore
|
|
248
|
+
abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore --sync-xml
|
|
247
249
|
|
|
248
250
|
# 3. Inspect AFTER pull (for errors or unsupported types)
|
|
249
251
|
abapgit-agent inspect --files src/zcl_class1.clas.abap
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
5
|
"files": [
|
|
6
6
|
"bin/",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:cmd:tree": "node tests/run-all.js --cmd --command=tree",
|
|
38
38
|
"test:cmd:dump": "node tests/run-all.js --cmd --command=dump",
|
|
39
39
|
"test:drop": "node tests/run-all.js --drop",
|
|
40
|
+
"test:customize": "node tests/run-all.js --customize",
|
|
40
41
|
"test:cmd:debug": "node tests/run-all.js --cmd --command=debug",
|
|
41
42
|
"test:debug": "node tests/run-all.js --debug",
|
|
42
43
|
"test:debug:scenarios": "bash tests/integration/debug-scenarios.sh",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customize command — write a single row to a SAP customizing table (delivery class C/E).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* abapgit-agent customize <table> --set <field=value> [<field=value>...]
|
|
6
|
+
* [--transport <TRKORR>] [--no-transport] [--json]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { printHttpError } = require('../utils/format-error');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
name: 'customize',
|
|
13
|
+
description: 'Write a row to a SAP customizing table (delivery class C/E)',
|
|
14
|
+
requiresAbapConfig: true,
|
|
15
|
+
requiresVersionCheck: false,
|
|
16
|
+
|
|
17
|
+
async execute(args, context) {
|
|
18
|
+
const { loadConfig, AbapHttp, getTransport, getTransportSettings } = context;
|
|
19
|
+
|
|
20
|
+
const jsonOutput = args.includes('--json');
|
|
21
|
+
const verbose = args.includes('--verbose');
|
|
22
|
+
const noTransport = args.includes('--no-transport');
|
|
23
|
+
|
|
24
|
+
// First positional arg (not starting with '-' and not following --set/--transport) = table name
|
|
25
|
+
const skipNext = new Set();
|
|
26
|
+
for (let i = 0; i < args.length; i++) {
|
|
27
|
+
if (args[i] === '--transport' || args[i] === '-t') {
|
|
28
|
+
if (i + 1 < args.length) skipNext.add(i + 1);
|
|
29
|
+
} else if (args[i] === '--set') {
|
|
30
|
+
let j = i + 1;
|
|
31
|
+
while (j < args.length && !args[j].startsWith('-')) {
|
|
32
|
+
skipNext.add(j);
|
|
33
|
+
j++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const tableName = args.find((a, idx) => !a.startsWith('-') && !skipNext.has(idx));
|
|
38
|
+
if (!tableName) {
|
|
39
|
+
console.error('❌ Error: table name is required');
|
|
40
|
+
console.error(' Usage: abapgit-agent customize <table> --set <field=value> [...]');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Collect all --set values (one or more field=value pairs after the flag)
|
|
45
|
+
const fieldValues = [];
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
if (args[i] === '--set') {
|
|
48
|
+
// Consume all following positional tokens as field=value pairs
|
|
49
|
+
let j = i + 1;
|
|
50
|
+
while (j < args.length && !args[j].startsWith('-')) {
|
|
51
|
+
const pair = args[j];
|
|
52
|
+
const eqIdx = pair.indexOf('=');
|
|
53
|
+
if (eqIdx === -1) {
|
|
54
|
+
console.error(`❌ Error: --set value '${pair}' must be in field=value format`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
fieldValues.push({
|
|
58
|
+
field: pair.substring(0, eqIdx).toUpperCase(),
|
|
59
|
+
value: pair.substring(eqIdx + 1)
|
|
60
|
+
});
|
|
61
|
+
j++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fieldValues.length === 0) {
|
|
67
|
+
console.error('❌ Error: at least one --set field=value pair is required');
|
|
68
|
+
console.error(' Example: abapgit-agent customize ZTABLE_CONFIG --set KEY=APP VALUE=active');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Transport resolution: CLI --transport > config/env > auto-selection
|
|
73
|
+
let transportRequest = null;
|
|
74
|
+
const transportArgIndex = args.indexOf('--transport');
|
|
75
|
+
if (transportArgIndex !== -1 && transportArgIndex + 1 < args.length) {
|
|
76
|
+
transportRequest = args[transportArgIndex + 1];
|
|
77
|
+
} else if (!noTransport) {
|
|
78
|
+
transportRequest = getTransport ? getTransport() : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// When no transport is determined yet and we are not in JSON mode, use the
|
|
82
|
+
// selector (hook / interactive picker) with type='customizing'
|
|
83
|
+
if (!transportRequest && !noTransport && !jsonOutput) {
|
|
84
|
+
const { selectTransport, isNonInteractive, _getTransportHookConfig } = require('../utils/transport-selector');
|
|
85
|
+
|
|
86
|
+
const config = loadConfig();
|
|
87
|
+
const http = new AbapHttp(config);
|
|
88
|
+
transportRequest = await selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings, 'customizing');
|
|
89
|
+
|
|
90
|
+
if (transportRequest === null) {
|
|
91
|
+
const hookConfig = _getTransportHookConfig();
|
|
92
|
+
if (hookConfig && hookConfig.hook) {
|
|
93
|
+
if (isNonInteractive()) {
|
|
94
|
+
console.error('❌ Error: transport hook returned no transport request.');
|
|
95
|
+
console.error(` Hook: ${hookConfig.hook}`);
|
|
96
|
+
if (hookConfig.description) console.error(` ${hookConfig.description}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
} else {
|
|
99
|
+
process.stderr.write(`⚠️ Transport hook returned no transport request (${hookConfig.hook}).\n`);
|
|
100
|
+
process.stderr.write(' Please select one manually:\n');
|
|
101
|
+
const { interactivePicker } = require('../utils/transport-selector');
|
|
102
|
+
transportRequest = await interactivePicker(http, 'customizing');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const config = loadConfig();
|
|
109
|
+
const http = new AbapHttp(config);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
113
|
+
const payload = {
|
|
114
|
+
table_name: tableName.toUpperCase(),
|
|
115
|
+
field_values: fieldValues,
|
|
116
|
+
transport: transportRequest || '',
|
|
117
|
+
no_transport: noTransport ? 'X' : ''
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = await http.post(
|
|
121
|
+
'/sap/bc/z_abapgit_agent/customize',
|
|
122
|
+
payload,
|
|
123
|
+
{ csrfToken }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (jsonOutput) {
|
|
127
|
+
console.log(JSON.stringify(result, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const success = result.SUCCESS === true || result.success === true ||
|
|
132
|
+
result.SUCCESS === 'X' || result.success === 'X';
|
|
133
|
+
const error = result.ERROR || result.error || '';
|
|
134
|
+
const message = result.MESSAGE || result.message || '';
|
|
135
|
+
const action = result.ACTION || result.action || '';
|
|
136
|
+
const transport = result.TRANSPORT || result.transport || '';
|
|
137
|
+
const deliveryCls = result.DELIVERY_CLASS || result.delivery_class || '';
|
|
138
|
+
|
|
139
|
+
if (!success) {
|
|
140
|
+
console.error(`❌ Error: ${error || message || 'customize command failed'}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`\n✅ Customizing entry written to ${tableName.toUpperCase()}`);
|
|
145
|
+
if (action) console.log(` Action: ${action}`);
|
|
146
|
+
if (transport) console.log(` Transport: ${transport} (recorded)`);
|
|
147
|
+
else if (deliveryCls) console.log(` Transport: none (delivery class ${deliveryCls})`);
|
|
148
|
+
else console.log(` Transport: none`);
|
|
149
|
+
if (message) console.log(` ${message}`);
|
|
150
|
+
console.log('');
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
printHttpError(error, { verbose });
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
package/src/commands/drop.js
CHANGED
|
@@ -8,7 +8,7 @@ const { printHttpError } = require('../utils/format-error');
|
|
|
8
8
|
const EXT_TO_TYPE = {
|
|
9
9
|
clas: 'CLAS', intf: 'INTF', prog: 'PROG', fugr: 'FUGR',
|
|
10
10
|
tabl: 'TABL', dtel: 'DTEL', ttyp: 'TTYP', doma: 'DOMA',
|
|
11
|
-
ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU'
|
|
11
|
+
ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU', enho: 'ENHO'
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
/**
|
package/src/commands/help.js
CHANGED
|
@@ -66,6 +66,9 @@ Commands:
|
|
|
66
66
|
List and manage SAP transport requests.
|
|
67
67
|
Subcommands: list, create, release
|
|
68
68
|
|
|
69
|
+
customize <table> --set <field>=<value> [<field>=<value> ...] [--transport <TRKORR>] [--no-transport] [--json]
|
|
70
|
+
Write a row (insert-or-update) into a SAP customizing table (delivery class C or E).
|
|
71
|
+
|
|
69
72
|
tree --package <package> [--depth <n>] [--include-types] [--json]
|
|
70
73
|
Display package hierarchy tree from ABAP system
|
|
71
74
|
|
|
@@ -147,6 +150,7 @@ Examples:
|
|
|
147
150
|
abapgit-agent view --objects ZCL_MY_CLASS # View class definition
|
|
148
151
|
abapgit-agent preview --objects SFLIGHT # Preview table data
|
|
149
152
|
abapgit-agent where --objects ZCL_MY_CLASS # Find where class is used
|
|
153
|
+
abapgit-agent customize ZAPP_CONFIG --set KEY=X VALUE=Y --no-transport # Write customizing entry
|
|
150
154
|
abapgit-agent dump --date TODAY # Recent short dumps
|
|
151
155
|
abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
|
|
152
156
|
abapgit-agent ref "CORRESPONDING" # Search for pattern
|
package/src/commands/index.js
CHANGED
package/src/commands/pull.js
CHANGED
|
@@ -153,8 +153,9 @@ Examples:
|
|
|
153
153
|
console.error('❌ Error: --files only accepts ABAP source files (.abap, .asddls) or XML-only object files (name.type.xml).');
|
|
154
154
|
console.error(' The following file(s) are not recognised:');
|
|
155
155
|
nonSourceFiles.forEach(f => console.error(` ${f}`));
|
|
156
|
-
console.error(' Tip: for source objects pass the .abap file
|
|
157
|
-
console.error('
|
|
156
|
+
console.error(' Tip: for source objects pass the .abap file (e.g. zcl_foo.clas.abap or zcl_foo.enho.28bbfe2f.abap);');
|
|
157
|
+
console.error(' for XML-only objects (TABL, STRU, DTEL, TTYP) or enhancement metadata,');
|
|
158
|
+
console.error(' pass the .xml file, e.g. --files abap/ztable.tabl.xml or abap/zfoo.enho.xml');
|
|
158
159
|
process.exit(1);
|
|
159
160
|
}
|
|
160
161
|
|
package/src/commands/syntax.js
CHANGED
|
@@ -24,7 +24,7 @@ Description:
|
|
|
24
24
|
Reads source from local files and checks directly in the ABAP system.
|
|
25
25
|
|
|
26
26
|
Parameters:
|
|
27
|
-
--files <file1,...> Comma-separated ABAP source files (required). Accepts CLAS, INTF, PROG.
|
|
27
|
+
--files <file1,...> Comma-separated ABAP source files (required). Accepts CLAS, INTF, PROG, ENHO.
|
|
28
28
|
--cloud Use ABAP Cloud (BTP) stricter syntax check.
|
|
29
29
|
--json Output as JSON.
|
|
30
30
|
|
|
@@ -103,6 +103,9 @@ Examples:
|
|
|
103
103
|
} else if (baseName.includes('.prog.')) {
|
|
104
104
|
objType = 'PROG';
|
|
105
105
|
objName = baseName.split('.')[0].toUpperCase();
|
|
106
|
+
} else if (/\.enho\.[0-9a-f]{8}\.abap$/i.test(baseName)) {
|
|
107
|
+
objType = 'ENHO';
|
|
108
|
+
objName = baseName.split('.')[0].toUpperCase();
|
|
106
109
|
} else if (baseName.includes('.ddls.asddls')) {
|
|
107
110
|
objType = 'DDLS';
|
|
108
111
|
objName = baseName.split('.')[0].toUpperCase();
|
|
@@ -81,7 +81,16 @@ module.exports = {
|
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const typeIdx = args.indexOf('--type');
|
|
85
|
+
const type = typeIdx !== -1 ? args[typeIdx + 1] : null;
|
|
86
|
+
|
|
87
|
+
if (type && !VALID_TYPES.includes(type)) {
|
|
88
|
+
console.error(`❌ Error: Invalid type '${type}'. Valid values: ${VALID_TYPES.join(', ')}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const typeParam = type ? `&type=${type}` : '';
|
|
93
|
+
const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}${typeParam}`);
|
|
85
94
|
|
|
86
95
|
if (jsonOutput) {
|
|
87
96
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -90,8 +99,9 @@ module.exports = {
|
|
|
90
99
|
|
|
91
100
|
const transports = result.TRANSPORTS || result.transports || [];
|
|
92
101
|
const scopeLabel = { mine: 'mine', task: 'task — where I own or have a task', all: 'all' }[scope];
|
|
102
|
+
const typeLabel = type ? ` (${type})` : '';
|
|
93
103
|
|
|
94
|
-
console.log(`\n📋 Open Transport Requests (${scopeLabel})\n`);
|
|
104
|
+
console.log(`\n📋 Open Transport Requests (${scopeLabel}${typeLabel})\n`);
|
|
95
105
|
|
|
96
106
|
if (transports.length === 0) {
|
|
97
107
|
console.log(' No open transport requests found.');
|
package/src/commands/view.js
CHANGED
|
@@ -156,9 +156,21 @@ function findFirstExecutableLine(lines) {
|
|
|
156
156
|
const commentPattern = /^\s*[*"]/;
|
|
157
157
|
// Program-level header/declaration keywords that are not executable statements
|
|
158
158
|
const progDeclPattern = /^\s*(report|program|parameters|tables|selection-screen|select-options|class-pool|function-pool|interface-pool|type-pool|include)\b/i;
|
|
159
|
+
// ENHO injected block marker: *"* ENHO: ... through *"* ENHO END
|
|
160
|
+
const enhoStartPattern = /^\s*\*"\* ENHO:/;
|
|
161
|
+
const enhoEndPattern = /^\s*\*"\* ENHO END/;
|
|
159
162
|
let inDeclBlock = false; // true while inside a multi-line DATA:/TYPES:/PARAMETERS: block
|
|
163
|
+
let inEnhoBlock = false; // true while inside an injected ENHO block
|
|
160
164
|
for (let i = 0; i < lines.length; i++) {
|
|
161
165
|
const trimmed = lines[i].trim();
|
|
166
|
+
if (inEnhoBlock) {
|
|
167
|
+
if (enhoEndPattern.test(trimmed)) inEnhoBlock = false;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (enhoStartPattern.test(trimmed)) {
|
|
171
|
+
inEnhoBlock = true;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
162
174
|
if (inDeclBlock) {
|
|
163
175
|
// continuation line — skip until the block closes with a period
|
|
164
176
|
if (trimmed.endsWith('.')) inDeclBlock = false;
|
|
@@ -182,6 +194,34 @@ function findFirstExecutableLine(lines) {
|
|
|
182
194
|
return 0;
|
|
183
195
|
}
|
|
184
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Count the number of injected ENHO lines in `lines[0..upToIndex-1]`.
|
|
199
|
+
* An ENHO block spans from a `*"* ENHO:` marker line through (and including) the
|
|
200
|
+
* `*"* ENHO END` marker line. All lines in these blocks are injected by the ABAP
|
|
201
|
+
* backend and do not exist in the base class source that ADT uses for BP validation.
|
|
202
|
+
*
|
|
203
|
+
* Subtracting this count from the execOffset returned by findFirstExecutableLine
|
|
204
|
+
* converts the injected-array index back to the base-source line offset, which is
|
|
205
|
+
* the coordinate system ADT accepts for breakpoints.
|
|
206
|
+
*/
|
|
207
|
+
function countEnhoLines(lines, upToIndex) {
|
|
208
|
+
const enhoStartPattern = /^\s*\*"\* ENHO:/;
|
|
209
|
+
const enhoEndPattern = /^\s*\*"\* ENHO END/;
|
|
210
|
+
let count = 0;
|
|
211
|
+
let inEnhoBlock = false;
|
|
212
|
+
for (let i = 0; i < upToIndex && i < lines.length; i++) {
|
|
213
|
+
const trimmed = lines[i].trim();
|
|
214
|
+
if (inEnhoBlock) {
|
|
215
|
+
count++;
|
|
216
|
+
if (enhoEndPattern.test(trimmed)) inEnhoBlock = false;
|
|
217
|
+
} else if (enhoStartPattern.test(trimmed)) {
|
|
218
|
+
inEnhoBlock = true;
|
|
219
|
+
count++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return count;
|
|
223
|
+
}
|
|
224
|
+
|
|
185
225
|
module.exports = {
|
|
186
226
|
name: 'view',
|
|
187
227
|
description: 'View ABAP object definitions from ABAP system',
|
|
@@ -189,6 +229,7 @@ module.exports = {
|
|
|
189
229
|
requiresVersionCheck: true,
|
|
190
230
|
_buildMethodLineMap: buildMethodLineMap,
|
|
191
231
|
_findFirstExecutableLine: findFirstExecutableLine,
|
|
232
|
+
_countEnhoLines: countEnhoLines,
|
|
192
233
|
|
|
193
234
|
async execute(args, context) {
|
|
194
235
|
const { loadConfig, AbapHttp } = context;
|
|
@@ -339,7 +380,8 @@ Examples:
|
|
|
339
380
|
const file = section.FILE || section.file || '';
|
|
340
381
|
const lines = section.LINES || section.lines || [];
|
|
341
382
|
const isCmSection = suffix.startsWith('CM') && methodName;
|
|
342
|
-
const
|
|
383
|
+
const isEnhoSection = /^[0-9a-f]{8}$/i.test(suffix);
|
|
384
|
+
const isFugrFmSection = !isCmSection && !isEnhoSection && !!methodName;
|
|
343
385
|
|
|
344
386
|
if (linesMode) {
|
|
345
387
|
// --full --lines: dual line numbers (G [N]) for debugging
|
|
@@ -359,7 +401,10 @@ Examples:
|
|
|
359
401
|
let bpHint;
|
|
360
402
|
if (globalStart) {
|
|
361
403
|
const execOffset = findFirstExecutableLine(lines);
|
|
362
|
-
|
|
404
|
+
// Subtract injected ENHO lines that appear before execOffset so
|
|
405
|
+
// the BP hint uses base-source coordinates (what ADT validates against)
|
|
406
|
+
const enhoCount = countEnhoLines(lines, execOffset);
|
|
407
|
+
const execLine = globalStart + execOffset - enhoCount;
|
|
363
408
|
bpHint = `debug set --objects ${objName}:${execLine}`;
|
|
364
409
|
} else {
|
|
365
410
|
bpHint = `debug set --objects ${objName}:<global_line>`;
|
|
@@ -390,6 +435,10 @@ Examples:
|
|
|
390
435
|
}
|
|
391
436
|
const bpHint = `debug set --objects ${suffix}:${firstExecLine}`;
|
|
392
437
|
console.log(` * ---- FM: ${methodName} (${suffix}) — breakpoint: ${bpHint} ----`);
|
|
438
|
+
} else if (isEnhoSection) {
|
|
439
|
+
const desc = section.DESCRIPTION || section.description || '';
|
|
440
|
+
console.log(` * ---- Hook: ${methodName} (${suffix}) ----`);
|
|
441
|
+
if (desc && desc !== methodName) console.log(` * full_name: ${desc}`);
|
|
393
442
|
} else if (file) {
|
|
394
443
|
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
395
444
|
} else if (suffix) {
|
|
@@ -453,6 +502,10 @@ Examples:
|
|
|
453
502
|
console.log(` * ---- Method: ${methodName} (${suffix}) ----`);
|
|
454
503
|
} else if (isFugrFmSection) {
|
|
455
504
|
console.log(` * ---- FM: ${methodName} (${suffix}) ----`);
|
|
505
|
+
} else if (isEnhoSection) {
|
|
506
|
+
const desc = section.DESCRIPTION || section.description || '';
|
|
507
|
+
console.log(` * ---- Hook: ${methodName} (${suffix}) ----`);
|
|
508
|
+
if (desc && desc !== methodName) console.log(` * full_name: ${desc}`);
|
|
456
509
|
} else if (file) {
|
|
457
510
|
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
458
511
|
} else if (suffix) {
|
|
@@ -38,11 +38,13 @@ async function runHook(hookPath, context) {
|
|
|
38
38
|
*
|
|
39
39
|
* @param {object} http
|
|
40
40
|
* @param {string} scope - 'mine' | 'task' | 'all'
|
|
41
|
+
* @param {string} [type] - 'workbench' (default) | 'customizing'
|
|
41
42
|
* @returns {Promise<Array>}
|
|
42
43
|
*/
|
|
43
|
-
async function fetchTransports(http, scope = 'mine') {
|
|
44
|
+
async function fetchTransports(http, scope = 'mine', type = 'workbench') {
|
|
44
45
|
try {
|
|
45
|
-
const
|
|
46
|
+
const typeParam = type === 'customizing' ? '&type=customizing' : '';
|
|
47
|
+
const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}${typeParam}`);
|
|
46
48
|
const raw = result.TRANSPORTS || result.transports || [];
|
|
47
49
|
return raw.map(t => ({
|
|
48
50
|
number: t.NUMBER || t.number || '',
|
|
@@ -59,13 +61,14 @@ async function fetchTransports(http, scope = 'mine') {
|
|
|
59
61
|
* Create a new transport request.
|
|
60
62
|
* @param {object} http
|
|
61
63
|
* @param {string} description
|
|
64
|
+
* @param {string} [type] - 'workbench' (default) | 'customizing'
|
|
62
65
|
* @returns {Promise<string|null>} transport number or null
|
|
63
66
|
*/
|
|
64
|
-
async function createTransport(http, description) {
|
|
67
|
+
async function createTransport(http, description, type = 'workbench') {
|
|
65
68
|
try {
|
|
66
69
|
const result = await http.post(
|
|
67
70
|
'/sap/bc/z_abapgit_agent/transport',
|
|
68
|
-
{ action: 'CREATE', description: description || '' },
|
|
71
|
+
{ action: 'CREATE', description: description || '', type },
|
|
69
72
|
{}
|
|
70
73
|
);
|
|
71
74
|
return result.NUMBER || result.number || null;
|
|
@@ -79,9 +82,10 @@ async function createTransport(http, description) {
|
|
|
79
82
|
* Presents a numbered menu; supports scope switching, create, and skip.
|
|
80
83
|
*
|
|
81
84
|
* @param {object} http
|
|
85
|
+
* @param {string} [type] - 'workbench' (default) | 'customizing'
|
|
82
86
|
* @returns {Promise<string|null>}
|
|
83
87
|
*/
|
|
84
|
-
async function interactivePicker(http) {
|
|
88
|
+
async function interactivePicker(http, type = 'workbench') {
|
|
85
89
|
const readline = require('readline');
|
|
86
90
|
|
|
87
91
|
let scope = 'mine';
|
|
@@ -92,11 +96,12 @@ async function interactivePicker(http) {
|
|
|
92
96
|
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
93
97
|
|
|
94
98
|
const fetchAndDisplay = async () => {
|
|
95
|
-
transports = await fetchTransports(http, scope);
|
|
99
|
+
transports = await fetchTransports(http, scope, type);
|
|
96
100
|
fetchError = transports.length === 0;
|
|
97
101
|
|
|
98
102
|
const scopeLabel = { mine: 'my transports', task: 'transports where I have a task', all: 'all open transports' }[scope];
|
|
99
|
-
|
|
103
|
+
const typeLabel = type === 'customizing' ? ' (customizing)' : '';
|
|
104
|
+
process.stderr.write(`\nSelect a transport request (showing: ${scopeLabel}${typeLabel}):\n\n`);
|
|
100
105
|
|
|
101
106
|
if (transports.length > 0) {
|
|
102
107
|
transports.forEach((t, i) => {
|
|
@@ -144,7 +149,7 @@ async function interactivePicker(http) {
|
|
|
144
149
|
if (answer === 'c') {
|
|
145
150
|
const desc = (await ask('Description: ')).trim();
|
|
146
151
|
rl.close();
|
|
147
|
-
const number = await createTransport(http, desc);
|
|
152
|
+
const number = await createTransport(http, desc, type);
|
|
148
153
|
if (number) {
|
|
149
154
|
process.stderr.write(`\n✅ Created transport ${number}\n`);
|
|
150
155
|
return number;
|
|
@@ -173,18 +178,30 @@ async function interactivePicker(http) {
|
|
|
173
178
|
*
|
|
174
179
|
* Splits on whitespace, forces --json, captures output, returns parsed JSON.
|
|
175
180
|
*
|
|
181
|
+
* When `type` is set, `transport list` calls automatically get `--type <type>`
|
|
182
|
+
* injected (unless the hook already passes --type explicitly). This means hooks
|
|
183
|
+
* written generically as `run('transport list ...')` automatically see only the
|
|
184
|
+
* right kind of transport (workbench or customizing) without any hook changes.
|
|
185
|
+
*
|
|
176
186
|
* @param {object} config - Loaded ABAP config
|
|
177
187
|
* @param {object} http - Pre-built AbapHttp instance
|
|
178
188
|
* @param {Function} loadConfig - Config factory (from pull context)
|
|
179
189
|
* @param {Function} AbapHttp - AbapHttp constructor (from pull context)
|
|
180
190
|
* @param {Function} getTransportSettings - Transport settings getter (from pull context)
|
|
191
|
+
* @param {string} [type] - 'workbench' (default) | 'customizing'
|
|
181
192
|
* @returns {Function|undefined} - The run helper, or undefined if factories are missing
|
|
182
193
|
*/
|
|
183
|
-
function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
|
|
194
|
+
function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings, type = 'workbench') {
|
|
184
195
|
if (!loadConfig || !AbapHttp) return undefined;
|
|
185
196
|
|
|
186
197
|
return async function run(command) {
|
|
187
|
-
|
|
198
|
+
let [commandName, ...args] = command.trim().split(/\s+/);
|
|
199
|
+
|
|
200
|
+
// Auto-inject --type for "transport list" so the hook gets the right request type
|
|
201
|
+
// without needing to know about the transport type explicitly.
|
|
202
|
+
if (commandName === 'transport' && args[0] === 'list' && !args.includes('--type') && type) {
|
|
203
|
+
args = [...args, '--type', type];
|
|
204
|
+
}
|
|
188
205
|
const cmdModule = require(`../commands/${commandName}`);
|
|
189
206
|
|
|
190
207
|
// Always force --json so output is parseable
|
|
@@ -220,7 +237,7 @@ function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
|
|
|
220
237
|
}
|
|
221
238
|
|
|
222
239
|
/**
|
|
223
|
-
* Main export — selects a transport request for use in the pull command.
|
|
240
|
+
* Main export — selects a transport request for use in the pull/customize command.
|
|
224
241
|
* Returns the transport number, or null to proceed without one.
|
|
225
242
|
*
|
|
226
243
|
* @param {object} config - Loaded ABAP config
|
|
@@ -228,16 +245,21 @@ function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
|
|
|
228
245
|
* @param {Function} [loadConfig] - Config factory (enables run helper in hook context)
|
|
229
246
|
* @param {Function} [AbapHttp] - AbapHttp constructor (enables run helper in hook context)
|
|
230
247
|
* @param {Function} [getTransportSettings] - Transport settings getter
|
|
248
|
+
* @param {string} [type] - 'workbench' (default) | 'customizing'
|
|
249
|
+
* Passed to hook as context.type and auto-injected into
|
|
250
|
+
* run('transport list ...') calls so hooks work correctly
|
|
251
|
+
* for both ABAP objects and customizing entries without
|
|
252
|
+
* needing to handle type explicitly.
|
|
231
253
|
* @returns {Promise<string|null>}
|
|
232
254
|
*/
|
|
233
|
-
async function selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings) {
|
|
255
|
+
async function selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings, type = 'workbench') {
|
|
234
256
|
// Hook takes precedence over the interactive picker — runs in both TTY and non-TTY mode
|
|
235
257
|
const hookConfig = module.exports._getTransportHookConfig();
|
|
236
258
|
if (hookConfig && hookConfig.hook) {
|
|
237
259
|
const hookPath = path.resolve(process.cwd(), hookConfig.hook);
|
|
238
|
-
const run = buildRun(config, http, loadConfig, AbapHttp, getTransportSettings);
|
|
260
|
+
const run = buildRun(config, http, loadConfig, AbapHttp, getTransportSettings, type);
|
|
239
261
|
try {
|
|
240
|
-
return await module.exports.runHook(hookPath, { config, http, run });
|
|
262
|
+
return await module.exports.runHook(hookPath, { config, http, run, type });
|
|
241
263
|
} catch {
|
|
242
264
|
return null;
|
|
243
265
|
}
|
|
@@ -249,7 +271,7 @@ async function selectTransport(config, http, loadConfig, AbapHttp, getTransportS
|
|
|
249
271
|
}
|
|
250
272
|
|
|
251
273
|
// Manual mode: interactive picker
|
|
252
|
-
return interactivePicker(http);
|
|
274
|
+
return interactivePicker(http, type);
|
|
253
275
|
}
|
|
254
276
|
|
|
255
277
|
/**
|