abapgit-agent 1.13.2 → 1.13.3
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 +9 -4
- package/abap/CLAUDE.slim.md +2 -0
- package/abap/guidelines/abapgit.md +143 -37
- package/package.json +2 -1
- package/src/commands/pull.js +70 -3
package/abap/CLAUDE.md
CHANGED
|
@@ -359,7 +359,7 @@ edit src/zcl_auth_handler.clas.abap
|
|
|
359
359
|
abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
|
|
360
360
|
git add . && git commit -m "feat: add authentication handler"
|
|
361
361
|
git push origin main
|
|
362
|
-
abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
362
|
+
abapgit-agent pull --files src/zcl_auth_handler.clas.abap --sync-xml
|
|
363
363
|
```
|
|
364
364
|
|
|
365
365
|
### AI Tool Guidelines
|
|
@@ -425,6 +425,11 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
425
425
|
1. ✗ Do not run `abapgit-agent transport release`
|
|
426
426
|
2. ✓ Inform the user that transport release is disabled for this project
|
|
427
427
|
|
|
428
|
+
**After every pull that creates or modifies ABAP objects:**
|
|
429
|
+
1. ✓ Always pass `--sync-xml` — rewrites any XML metadata files that differ from the ABAP serializer output, amends the commit, and re-pulls so git and the ABAP system stay in sync
|
|
430
|
+
2. ✓ If pull output shows `⚠️ X XML file(s) differ from serializer output`, re-run immediately with `--sync-xml`
|
|
431
|
+
3. ✗ Never leave a pull without `--sync-xml` when you authored the objects — abapGit will show **M (modified)** permanently otherwise
|
|
432
|
+
|
|
428
433
|
---
|
|
429
434
|
|
|
430
435
|
### Quick Decision Tree for AI
|
|
@@ -435,11 +440,11 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
435
440
|
Modified ABAP files?
|
|
436
441
|
├─ CLAS/INTF/PROG/DDLS files?
|
|
437
442
|
│ ├─ Independent files (no cross-dependencies)?
|
|
438
|
-
│ │ └─ ✅ Use: syntax → commit → push → pull
|
|
443
|
+
│ │ └─ ✅ Use: syntax → commit → push → pull --sync-xml
|
|
439
444
|
│ └─ Dependent files (interface + class, class uses class)?
|
|
440
|
-
│ └─ ✅ Use: skip syntax → commit → push → pull
|
|
445
|
+
│ └─ ✅ Use: skip syntax → commit → push → pull --sync-xml
|
|
441
446
|
└─ Other types (DDLS, FUGR, TABL, etc.)?
|
|
442
|
-
└─ ✅ Use: skip syntax → commit → push → pull → (if errors: inspect)
|
|
447
|
+
└─ ✅ Use: skip syntax → commit → push → pull --sync-xml → (if errors: inspect)
|
|
443
448
|
```
|
|
444
449
|
|
|
445
450
|
→ See `guidelines/workflow-detailed.md` — run: `abapgit-agent ref --topic workflow-detailed`
|
package/abap/CLAUDE.slim.md
CHANGED
|
@@ -10,6 +10,8 @@ Read the full ABAP development guide by running:
|
|
|
10
10
|
abapgit-agent guide
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
**Never pipe `abapgit-agent guide` or `abapgit-agent ref` through `head`, `tail`, or any other truncation command. Always read the full output.**
|
|
14
|
+
|
|
13
15
|
This guide covers: development workflow, ABAP syntax guidelines, object naming, unit testing, and debugging.
|
|
14
16
|
|
|
15
17
|
> **Humans:** Full guide online at https://sylvoscai.github.io/abapgit-agent/pages/abap-coding-guidelines.html
|
|
@@ -26,16 +26,12 @@ Structure | z*.stru.abap | z*.stru.xml
|
|
|
26
26
|
Table Type | z*.ttyp.abap | z*.ttyp.xml
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
CDS SOURCE_TYPE: W=View Entity (modern), V=View (legacy)
|
|
36
|
-
Test Class XML: <WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
|
|
37
|
-
Local Classes: <CLSCCINCL>X</CLSCCINCL>
|
|
38
|
-
```
|
|
29
|
+
> **CRITICAL: Always write XML files with a UTF-8 BOM (`\ufeff`) as the very first character**, before `<?xml ...`.
|
|
30
|
+
> Without the BOM, abapGit shows the object as **"M" (modified)** after every pull because the
|
|
31
|
+
> serializer always produces XML with BOM — and every byte matters.
|
|
32
|
+
|
|
33
|
+
> **CRITICAL: Only include fields that abapGit's serializer actually writes. Never add fields with
|
|
34
|
+
> default values.** Extra fields cause a permanent "M" (modified) diff. Follow the exact templates below.
|
|
39
35
|
|
|
40
36
|
**Searchable keywords**: class xml, interface xml, table xml, cds xml, test class, exposure, serializer, abapgit
|
|
41
37
|
|
|
@@ -46,14 +42,50 @@ abapGit needs XML files to:
|
|
|
46
42
|
- Store object attributes (description, exposure, state, etc.)
|
|
47
43
|
- Handle object-specific configurations
|
|
48
44
|
|
|
45
|
+
## Field Presence Rules (CRITICAL)
|
|
46
|
+
|
|
47
|
+
abapGit's serializer **omits fields that have their default value**. Writing extra fields causes permanent
|
|
48
|
+
"M" (modified) status in abapGit UI. Follow these rules strictly:
|
|
49
|
+
|
|
50
|
+
### CLAS — Field Presence Rules
|
|
51
|
+
|
|
52
|
+
| Field | Include when | Omit when |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `CLSNAME` | Always | — |
|
|
55
|
+
| `LANGU` | Always | — |
|
|
56
|
+
| `DESCRIPT` | Always | — |
|
|
57
|
+
| `CATEGORY` | Non-standard class (`40`=exception, `05`=test double) | Standard class (default `00`) — **omit** |
|
|
58
|
+
| `EXPOSURE` | **Never for CLAS** | **Always omit** — public (`2`) is the default and is never written |
|
|
59
|
+
| `STATE` | Always (`1`) | — |
|
|
60
|
+
| `CLSCCINCL` | `.clas.locals_def.abap` file exists | No local class files — **omit** |
|
|
61
|
+
| `FIXPT` | Always (`X`) | — |
|
|
62
|
+
| `UNICODE` | Always (`X`) | — |
|
|
63
|
+
| `WITH_UNIT_TESTS` | `.clas.testclasses.abap` file exists | No test class file — **omit** |
|
|
64
|
+
| `MSG_ID` | Class has a message class | No message class — **omit** |
|
|
65
|
+
|
|
66
|
+
**Field order**: `CLSNAME → LANGU → DESCRIPT → [CATEGORY] → STATE → [CLSCCINCL] → FIXPT → UNICODE → [WITH_UNIT_TESTS] → [MSG_ID]`
|
|
67
|
+
|
|
68
|
+
### INTF — Field Presence Rules
|
|
69
|
+
|
|
70
|
+
| Field | Include when | Omit when |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `CLSNAME` | Always | — |
|
|
73
|
+
| `LANGU` | Always | — |
|
|
74
|
+
| `DESCRIPT` | Always | — |
|
|
75
|
+
| `EXPOSURE` | Always (`2`) | — (interfaces always have EXPOSURE, unlike classes) |
|
|
76
|
+
| `STATE` | Always (`1`) | — |
|
|
77
|
+
| `UNICODE` | Always (`X`) | — |
|
|
78
|
+
|
|
49
79
|
## Object Types and XML Templates
|
|
50
80
|
|
|
51
81
|
### Class (CLAS)
|
|
52
82
|
|
|
53
83
|
**Filename**: `src/zcl_my_class.clas.xml`
|
|
54
84
|
|
|
85
|
+
**Standard public class** (no local includes, no test class):
|
|
86
|
+
|
|
55
87
|
```xml
|
|
56
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
88
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
57
89
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
58
90
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
59
91
|
<asx:values>
|
|
@@ -61,29 +93,85 @@ abapGit needs XML files to:
|
|
|
61
93
|
<CLSNAME>ZCL_MY_CLASS</CLSNAME>
|
|
62
94
|
<LANGU>E</LANGU>
|
|
63
95
|
<DESCRIPT>Description of the class</DESCRIPT>
|
|
64
|
-
<EXPOSURE>2</EXPOSURE>
|
|
65
96
|
<STATE>1</STATE>
|
|
97
|
+
<FIXPT>X</FIXPT>
|
|
66
98
|
<UNICODE>X</UNICODE>
|
|
99
|
+
</VSEOCLASS>
|
|
100
|
+
</asx:values>
|
|
101
|
+
</asx:abap>
|
|
102
|
+
</abapGit>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Class with test class** (`.clas.testclasses.abap` exists — add `WITH_UNIT_TESTS`):
|
|
106
|
+
|
|
107
|
+
```xml
|
|
108
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
109
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
110
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
111
|
+
<asx:values>
|
|
112
|
+
<VSEOCLASS>
|
|
113
|
+
<CLSNAME>ZCL_MY_CLASS</CLSNAME>
|
|
114
|
+
<LANGU>E</LANGU>
|
|
115
|
+
<DESCRIPT>Description of the class</DESCRIPT>
|
|
116
|
+
<STATE>1</STATE>
|
|
67
117
|
<FIXPT>X</FIXPT>
|
|
118
|
+
<UNICODE>X</UNICODE>
|
|
119
|
+
<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
|
|
68
120
|
</VSEOCLASS>
|
|
69
121
|
</asx:values>
|
|
70
122
|
</asx:abap>
|
|
71
123
|
</abapGit>
|
|
72
124
|
```
|
|
73
125
|
|
|
74
|
-
**
|
|
75
|
-
- `CLSNAME`: Class name (must match filename)
|
|
76
|
-
- `DESCRIPT`: Class description
|
|
77
|
-
- `EXPOSURE`: Exposure (2 = Public, 3 = Protected, 4 = Private)
|
|
78
|
-
- `STATE`: State (1 = Active)
|
|
79
|
-
- `UNICODE`: Unicode encoding (X = Yes)
|
|
80
|
-
- `FIXPT`: Fixed-point arithmetic (X = Yes) - **Always include for correct decimal arithmetic**
|
|
126
|
+
**Class with local includes** (`.clas.locals_def.abap` exists — add `CLSCCINCL` before `FIXPT`):
|
|
81
127
|
|
|
82
|
-
|
|
128
|
+
```xml
|
|
129
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
130
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
131
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
132
|
+
<asx:values>
|
|
133
|
+
<VSEOCLASS>
|
|
134
|
+
<CLSNAME>ZCL_MY_CLASS</CLSNAME>
|
|
135
|
+
<LANGU>E</LANGU>
|
|
136
|
+
<DESCRIPT>Description of the class</DESCRIPT>
|
|
137
|
+
<STATE>1</STATE>
|
|
138
|
+
<CLSCCINCL>X</CLSCCINCL>
|
|
139
|
+
<FIXPT>X</FIXPT>
|
|
140
|
+
<UNICODE>X</UNICODE>
|
|
141
|
+
<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
|
|
142
|
+
</VSEOCLASS>
|
|
143
|
+
</asx:values>
|
|
144
|
+
</asx:abap>
|
|
145
|
+
</abapGit>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Exception class** (`CATEGORY>40` — add `CATEGORY` after `DESCRIPT`):
|
|
149
|
+
|
|
150
|
+
```xml
|
|
151
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
152
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
153
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
154
|
+
<asx:values>
|
|
155
|
+
<VSEOCLASS>
|
|
156
|
+
<CLSNAME>ZCX_MY_EXCEPTION</CLSNAME>
|
|
157
|
+
<LANGU>E</LANGU>
|
|
158
|
+
<DESCRIPT>My exception</DESCRIPT>
|
|
159
|
+
<CATEGORY>40</CATEGORY>
|
|
160
|
+
<STATE>1</STATE>
|
|
161
|
+
<CLSCCINCL>X</CLSCCINCL>
|
|
162
|
+
<FIXPT>X</FIXPT>
|
|
163
|
+
<UNICODE>X</UNICODE>
|
|
164
|
+
</VSEOCLASS>
|
|
165
|
+
</asx:values>
|
|
166
|
+
</asx:abap>
|
|
167
|
+
</abapGit>
|
|
168
|
+
```
|
|
83
169
|
|
|
84
|
-
**
|
|
85
|
-
- `<
|
|
86
|
-
- `<
|
|
170
|
+
**Key rules**:
|
|
171
|
+
- ❌ **Never include `<EXPOSURE>`** in a CLAS XML — public (2) is the default and abapGit omits it
|
|
172
|
+
- ✅ Always include `<FIXPT>X</FIXPT>` and `<UNICODE>X</UNICODE>`
|
|
173
|
+
- ✅ Add `<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>` only when `.clas.testclasses.abap` exists
|
|
174
|
+
- ✅ Add `<CLSCCINCL>X</CLSCCINCL>` only when `.clas.locals_def.abap` exists
|
|
87
175
|
|
|
88
176
|
**Local Class Files**:
|
|
89
177
|
| File | Purpose |
|
|
@@ -98,7 +186,7 @@ abapGit needs XML files to:
|
|
|
98
186
|
**Filename**: `src/zif_my_interface.intf.xml`
|
|
99
187
|
|
|
100
188
|
```xml
|
|
101
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
189
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
102
190
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_INTF" serializer_version="v1.0.0">
|
|
103
191
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
104
192
|
<asx:values>
|
|
@@ -115,6 +203,9 @@ abapGit needs XML files to:
|
|
|
115
203
|
</abapGit>
|
|
116
204
|
```
|
|
117
205
|
|
|
206
|
+
**Key rules**:
|
|
207
|
+
- ✅ `<EXPOSURE>2</EXPOSURE>` is **always present** for interfaces (unlike classes where it is omitted)
|
|
208
|
+
|
|
118
209
|
---
|
|
119
210
|
|
|
120
211
|
### Program (PROG)
|
|
@@ -122,13 +213,13 @@ abapGit needs XML files to:
|
|
|
122
213
|
**Filename**: `src/zmy_program.prog.xml`
|
|
123
214
|
|
|
124
215
|
```xml
|
|
125
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
216
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
126
217
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_PROG" serializer_version="v1.0.0">
|
|
127
218
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
128
219
|
<asx:values>
|
|
129
220
|
<PROGDIR>
|
|
130
221
|
<NAME>ZMY_PROGRAM</NAME>
|
|
131
|
-
<SUBC>
|
|
222
|
+
<SUBC>1</SUBC>
|
|
132
223
|
<RLOAD>E</RLOAD>
|
|
133
224
|
<FIXPT>X</FIXPT>
|
|
134
225
|
<UCCHECK>X</UCCHECK>
|
|
@@ -140,8 +231,12 @@ abapGit needs XML files to:
|
|
|
140
231
|
|
|
141
232
|
**Key Fields**:
|
|
142
233
|
- `NAME`: Program name
|
|
143
|
-
- `SUBC`:
|
|
144
|
-
- `RLOAD`:
|
|
234
|
+
- `SUBC`: Program type (`1`=Executable, `I`=Include, `F`=Function Group, `M`=Module Pool, `S`=Subroutine Pool)
|
|
235
|
+
- `RLOAD`: `E`=External
|
|
236
|
+
- `FIXPT`: Fixed-point arithmetic (`X`=Yes) — include for executables
|
|
237
|
+
- `UCCHECK`: Unicode checks active (`X`=Yes)
|
|
238
|
+
|
|
239
|
+
**Note**: The serializer may also write a `<TPOOL>` section after `<PROGDIR>` if the program has a title text. You do not need to write this when creating new programs — abapGit will add it on the next pull if needed.
|
|
145
240
|
|
|
146
241
|
---
|
|
147
242
|
|
|
@@ -150,7 +245,7 @@ abapGit needs XML files to:
|
|
|
150
245
|
**Filename**: `src/zmy_table.tabl.xml`
|
|
151
246
|
|
|
152
247
|
```xml
|
|
153
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
248
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
154
249
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_TABL" serializer_version="v1.0.0">
|
|
155
250
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
156
251
|
<asx:values>
|
|
@@ -168,9 +263,20 @@ abapGit needs XML files to:
|
|
|
168
263
|
|
|
169
264
|
**Key Fields**:
|
|
170
265
|
- `TABNAME`: Table name
|
|
171
|
-
- `DDTEXT`: Description (
|
|
172
|
-
- `TABCLASS`:
|
|
173
|
-
- `CONTFLAG`: Delivery class
|
|
266
|
+
- `DDTEXT`: Description (**not** `DESCRIPT`)
|
|
267
|
+
- `TABCLASS`: `TRANSP`=Transparent (most common), `POOL`, `CLUSTER`
|
|
268
|
+
- `CONTFLAG`: Delivery class — `A`=Application, `C`=Customizing, `S`=System, `G`=Customizing protected
|
|
269
|
+
|
|
270
|
+
**Note**: When abapGit serializes an existing table it also writes `<DD09L>` (technical settings) and `<DD03P_TABLE>` (field definitions). These sections are generated automatically from the ABAP Dictionary on pull — you only need the `<DD02V>` header when creating a new table. After the first pull the XML will be expanded with those sections.
|
|
271
|
+
|
|
272
|
+
**`DD03P` field-level rules** (apply when editing an existing table XML that includes `<DD03P_TABLE>`):
|
|
273
|
+
|
|
274
|
+
| Rule | Detail |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `SHLPORIGIN` | Include `<SHLPORIGIN>D</SHLPORIGIN>` on fields where the Dictionary provides a value help (e.g. fields with a domain that has fixed values or a search help). Omit on fields with no value help. |
|
|
277
|
+
| Field order for raw-type fields | For fields with no `ROLLNAME` (raw type, e.g. `CHAR`, `NUMC`), the serializer writes `<MASK>` **before** `<DDTEXT>`. For fields with a `ROLLNAME`, only `ROLLNAME` appears (no `MASK` or `DDTEXT`). |
|
|
278
|
+
|
|
279
|
+
**Why this matters**: Missing `SHLPORIGIN` or wrong `MASK`/`DDTEXT` order causes a permanent diff between git and the system-serialized XML.
|
|
174
280
|
|
|
175
281
|
---
|
|
176
282
|
|
|
@@ -181,7 +287,7 @@ abapGit needs XML files to:
|
|
|
181
287
|
The XML format is identical for both types — only `SOURCE_TYPE` differs:
|
|
182
288
|
|
|
183
289
|
```xml
|
|
184
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
290
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
185
291
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_DDLS" serializer_version="v1.0.0">
|
|
186
292
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
187
293
|
<asx:values>
|
|
@@ -208,7 +314,7 @@ The XML format is identical for both types — only `SOURCE_TYPE` differs:
|
|
|
208
314
|
**Filename**: `src/zmy_dtel.dtel.xml`
|
|
209
315
|
|
|
210
316
|
```xml
|
|
211
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
317
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
212
318
|
<abapGit version="v1.0.0" serializer="LCL_OBJECT_DTEL" serializer_version="v1.0.0">
|
|
213
319
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
214
320
|
<asx:values>
|
|
@@ -231,9 +337,9 @@ The XML format is identical for both types — only `SOURCE_TYPE` differs:
|
|
|
231
337
|
|
|
232
338
|
**Key Fields**:
|
|
233
339
|
- `ROLLNAME`: Data element name
|
|
234
|
-
- `DDTEXT`: Description (
|
|
235
|
-
- `DATATYPE`: Data type (CHAR
|
|
236
|
-
- `LENG`: Length (e.g
|
|
340
|
+
- `DDTEXT`: Description (**not** `DESCRIPT`)
|
|
341
|
+
- `DATATYPE`: Data type (`CHAR`, `NUMC`, `DATS`, `TIMS`, `INT4`, etc.)
|
|
342
|
+
- `LENG`: Length padded to 6 digits (e.g. `000010` for 10 characters)
|
|
237
343
|
|
|
238
344
|
---
|
|
239
345
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.3",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
5
|
"files": [
|
|
6
6
|
"bin/",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test:cmd:demo": "node tests/run-all.js --cmd --demo",
|
|
28
28
|
"test:cmd:syntax": "node tests/run-all.js --cmd --command=syntax",
|
|
29
29
|
"test:cmd:pull": "node tests/run-all.js --cmd --command=pull",
|
|
30
|
+
"test:sync-xml": "node tests/run-all.js --sync-xml",
|
|
30
31
|
"test:cmd:inspect": "node tests/run-all.js --cmd --command=inspect",
|
|
31
32
|
"test:cmd:unit": "node tests/run-all.js --cmd --command=unit",
|
|
32
33
|
"test:cmd:view": "node tests/run-all.js --cmd --command=view",
|
package/src/commands/pull.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const { printHttpError } = require('../utils/format-error');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const pathModule = require('path');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
8
9
|
|
|
9
10
|
// Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
|
|
10
11
|
function calcWidth(str) {
|
|
@@ -42,6 +43,7 @@ module.exports = {
|
|
|
42
43
|
async execute(args, context) {
|
|
43
44
|
const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards, getConflictSettings, getTransportSettings } = context;
|
|
44
45
|
const verbose = args.includes('--verbose');
|
|
46
|
+
const syncXml = args.includes('--sync-xml');
|
|
45
47
|
|
|
46
48
|
// Check project-level safeguards
|
|
47
49
|
const safeguards = getSafeguards();
|
|
@@ -198,13 +200,13 @@ module.exports = {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, undefined, conflictMode, verbose);
|
|
203
|
+
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, undefined, conflictMode, verbose, syncXml);
|
|
202
204
|
},
|
|
203
205
|
|
|
204
|
-
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined, conflictMode = 'abort', verbose = false) {
|
|
206
|
+
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined, conflictMode = 'abort', verbose = false, syncXml = false, isRepull = false) {
|
|
205
207
|
const TERM_WIDTH = process.stdout.columns || 80;
|
|
206
208
|
|
|
207
|
-
if (!jsonOutput) {
|
|
209
|
+
if (!jsonOutput && !isRepull) {
|
|
208
210
|
console.log(`\n🚀 Starting pull for: ${gitUrl}`);
|
|
209
211
|
console.log(` Branch: ${branch}`);
|
|
210
212
|
if (files && files.length > 0) {
|
|
@@ -438,6 +440,71 @@ module.exports = {
|
|
|
438
440
|
throw err;
|
|
439
441
|
}
|
|
440
442
|
|
|
443
|
+
// --- Post-pull XML sync ---
|
|
444
|
+
// abapGit's status calculation already identified which XML files differ
|
|
445
|
+
// (match=false) — only those are returned in local_xml_files.
|
|
446
|
+
const localXmlFiles = result.local_xml_files || result.LOCAL_XML_FILES || [];
|
|
447
|
+
|
|
448
|
+
if (localXmlFiles.length > 0) {
|
|
449
|
+
const diffFiles = [];
|
|
450
|
+
for (const f of localXmlFiles) {
|
|
451
|
+
const relPath = ((f.path || f.PATH || '') + (f.filename || f.FILENAME || '')).replace(/^\//, '');
|
|
452
|
+
const absPath = pathModule.join(process.cwd(), relPath);
|
|
453
|
+
if (!fs.existsSync(absPath)) continue;
|
|
454
|
+
const incoming = Buffer.from(f.data || f.DATA, 'base64');
|
|
455
|
+
// Double-check: only write if bytes actually differ (guard against encoding quirks)
|
|
456
|
+
const current = fs.readFileSync(absPath);
|
|
457
|
+
if (!current.equals(incoming)) {
|
|
458
|
+
diffFiles.push({ relPath, absPath, incoming });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (diffFiles.length > 0 && !syncXml) {
|
|
463
|
+
console.log(`\n⚠️ ${diffFiles.length} XML file(s) differ from serializer output:`);
|
|
464
|
+
for (const f of diffFiles) console.log(` ${f.relPath}`);
|
|
465
|
+
console.log(` Run with --sync-xml to accept serializer output and amend the last commit`);
|
|
466
|
+
} else if (diffFiles.length > 0 && syncXml) {
|
|
467
|
+
console.log(`\n🔄 Syncing ${diffFiles.length} XML file(s) to match serializer output:`);
|
|
468
|
+
for (const f of diffFiles) console.log(` ${f.relPath}`);
|
|
469
|
+
|
|
470
|
+
// 1. Write serializer XML to disk
|
|
471
|
+
for (const f of diffFiles) fs.writeFileSync(f.absPath, f.incoming);
|
|
472
|
+
|
|
473
|
+
// 2. Stage changed XML files
|
|
474
|
+
const quotedPaths = diffFiles.map(f => `"${f.relPath}"`).join(' ');
|
|
475
|
+
execSync(`git add ${quotedPaths}`, { cwd: process.cwd() });
|
|
476
|
+
|
|
477
|
+
// 3. Amend last commit
|
|
478
|
+
execSync('git commit --amend --no-edit', { cwd: process.cwd() });
|
|
479
|
+
|
|
480
|
+
// 4. Push with force-with-lease; if no upstream, set it automatically
|
|
481
|
+
let pushed = false;
|
|
482
|
+
try {
|
|
483
|
+
execSync('git push --force-with-lease', { cwd: process.cwd(), stdio: 'pipe' });
|
|
484
|
+
pushed = true;
|
|
485
|
+
} catch (pushErr) {
|
|
486
|
+
const msg = (pushErr.stderr || pushErr.stdout || pushErr.message || '').toString();
|
|
487
|
+
if (msg.includes('no upstream branch') || msg.includes('has no upstream')) {
|
|
488
|
+
// Branch not yet pushed — set upstream and force push (amend requires force)
|
|
489
|
+
execSync(`git push --force-with-lease --set-upstream origin ${branch}`, { cwd: process.cwd(), stdio: 'pipe' });
|
|
490
|
+
pushed = true;
|
|
491
|
+
}
|
|
492
|
+
// Any other push error (no remote at all, auth failure, etc.) → skip silently
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (pushed) {
|
|
496
|
+
console.log(` Re-pulling so ABAP system matches the amended commit...`);
|
|
497
|
+
// 5. Re-pull so ABAP system matches the amended commit (no sync loop)
|
|
498
|
+
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, gitCredentials, conflictMode, verbose, false, true);
|
|
499
|
+
console.log(`\n✅ Synced ${diffFiles.length} XML file(s), amended commit, re-pulled`);
|
|
500
|
+
} else {
|
|
501
|
+
// No remote at all — files are written and committed locally
|
|
502
|
+
console.log(`\n✅ Synced ${diffFiles.length} XML file(s), amended commit`);
|
|
503
|
+
console.log(` Push skipped (no remote). Push manually to sync with remote.`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
441
508
|
return result;
|
|
442
509
|
} catch (error) {
|
|
443
510
|
if (error._isPullError) {
|