abapgit-agent 1.8.8 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,6 +144,10 @@ abapgit-agent preview --objects SFLIGHT --compact
144
144
  abapgit-agent where --objects ZCL_MY_CLASS
145
145
  abapgit-agent where --objects ZIF_MY_INTERFACE
146
146
  abapgit-agent where --objects ZCL_MY_CLASS --type CLAS
147
+
148
+ # Query short dumps (ST22)
149
+ abapgit-agent dump --date TODAY
150
+ abapgit-agent dump --user DEVELOPER --date TODAY --detail 1
147
151
  ```
148
152
 
149
153
  ### Utility Commands
@@ -188,6 +192,7 @@ npm run pull -- --url <git-url> --branch main
188
192
  | view Command | [docs/view-command.md](docs/view-command.md) |
189
193
  | preview Command | [docs/preview-command.md](docs/preview-command.md) |
190
194
  | where Command | [docs/where-command.md](docs/where-command.md) |
195
+ | dump Command | [docs/dump-command.md](docs/dump-command.md) |
191
196
  | ref Command | [docs/ref-command.md](docs/ref-command.md) |
192
197
  | REST API Reference | [API.md](API.md) |
193
198
  | Error Handling | [ERROR_HANDLING.md](ERROR_HANDLING.md) |
package/abap/CLAUDE.md CHANGED
@@ -256,6 +256,110 @@ abapgit-agent unit --files src/zcl_test1.clas.testclasses.abap,src/zcl_test2.cla
256
256
 
257
257
  ---
258
258
 
259
+ ### 9. Use `dump` Command to Investigate Runtime Errors (ST22)
260
+
261
+ **When a user reports an ABAP runtime error, OR when any command returns HTTP 500 / an unexpected ABAP error, proactively check for short dumps.**
262
+
263
+ ```
264
+ ❌ WRONG: Ask user to open ST22 transaction manually
265
+ ❌ WRONG: Give up after an HTTP 500 without checking what caused it
266
+ ✅ CORRECT: Use abapgit-agent dump to query short dumps and find the root cause
267
+ ```
268
+
269
+ #### When to Use `dump`
270
+
271
+ | Scenario | Action |
272
+ |----------|--------|
273
+ | User reports "there was a dump in production" | `dump --user <user> --date TODAY` |
274
+ | Any command returns HTTP 500 or unexpected error | `dump --date TODAY` (check for recent dumps) |
275
+ | `pull` or `inspect` fails with internal ABAP error | `dump --date TODAY` |
276
+ | Test run fails with runtime error | `dump --program <program> --date TODAY` |
277
+ | Investigating TIME_OUT or other specific error | `dump --error TIME_OUT` |
278
+ | Finding all errors from a specific user | `dump --user DEVELOPER` |
279
+ | Full error analysis with source location | `dump --detail 1` (after listing) |
280
+
281
+ #### Typical Investigation Workflow
282
+
283
+ ```bash
284
+ # Step 1: List recent dumps (last 7 days by default)
285
+ abapgit-agent dump
286
+
287
+ # Step 2: Narrow by program or user if known
288
+ abapgit-agent dump --program ZMY_PROGRAM --date TODAY
289
+
290
+ # Step 3: View full details of the first result
291
+ abapgit-agent dump --program ZMY_PROGRAM --date TODAY --detail 1
292
+ ```
293
+
294
+ The `--detail` output includes:
295
+ - **What happened** — high-level error description
296
+ - **Error analysis** — detailed SAP explanation
297
+ - **Call stack** — method-by-method trace with line numbers
298
+ - **Source with `>>>>>` marker** — exact line where the dump occurred
299
+
300
+ #### Reading Detail Output
301
+
302
+ ```
303
+ Short Dump Detail
304
+
305
+ Error MESSAGE_TYPE_X
306
+ Date 2024-01-15 (Europe/Berlin)
307
+ Time 09:26:53
308
+ User DEVELOPER
309
+ Program ZMY_PROGRAM
310
+
311
+ What happened:
312
+ -------------------------------------------------------
313
+ A RAISE statement raised the exception "MESSAGE_TYPE_X".
314
+
315
+ Call stack:
316
+ -------------------------------------------------------
317
+ 1 ZCL_MY_CLASS->DO_SOMETHING (line 42)
318
+ 2 ZMY_PROGRAM START-OF-SELECTION (line 5)
319
+
320
+ Source (ZCL_MY_CLASS=============CM003, line 42):
321
+ -------------------------------------------------------
322
+ METHOD do_something.
323
+ DATA lv_val TYPE i.
324
+ >>>>> RAISE EXCEPTION TYPE cx_my_error. ← error here
325
+ lv_val = lv_val + 1.
326
+ ENDMETHOD.
327
+ ```
328
+
329
+ **After identifying the error location**, use `view` to understand the class context:
330
+
331
+ ```bash
332
+ # View the class/method where the dump occurred
333
+ abapgit-agent view --objects ZCL_MY_CLASS
334
+ ```
335
+
336
+ #### Filters Reference
337
+
338
+ ```bash
339
+ # Filter by user
340
+ abapgit-agent dump --user DEVELOPER
341
+
342
+ # Filter by date (system timezone)
343
+ abapgit-agent dump --date TODAY
344
+ abapgit-agent dump --date YESTERDAY
345
+ abapgit-agent dump --date 2024-01-01..2024-01-31
346
+
347
+ # Filter by program
348
+ abapgit-agent dump --program ZMY_PROGRAM
349
+
350
+ # Filter by error type
351
+ abapgit-agent dump --error TIME_OUT
352
+ abapgit-agent dump --error MESSAGE_TYPE_X
353
+
354
+ # Show full detail for result #N (combine with same filters)
355
+ abapgit-agent dump --user DEVELOPER --date TODAY --detail 1
356
+
357
+ # Use explicit timezone (e.g., for distributed teams)
358
+ abapgit-agent dump --date TODAY --timezone Europe/Berlin
359
+ ```
360
+
361
+ ---
362
+
259
363
  ## Development Workflow
260
364
 
261
365
  This project's workflow mode is configured in `.abapGitAgent` under `workflow.mode`.
@@ -672,6 +776,13 @@ abapgit-agent view --objects ZTABLE --type TABL
672
776
 
673
777
  # Display package tree
674
778
  abapgit-agent tree --package \$MY_PACKAGE
779
+
780
+ # Investigate runtime errors (ST22 short dumps)
781
+ abapgit-agent dump # Last 7 days
782
+ abapgit-agent dump --user DEVELOPER --date TODAY # Today's dumps for a user
783
+ abapgit-agent dump --program ZMY_PROGRAM # Dumps from a specific program
784
+ abapgit-agent dump --error TIME_OUT # Dumps by error type
785
+ abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
675
786
  ```
676
787
 
677
788
  ---
package/bin/abapgit-agent CHANGED
@@ -50,6 +50,7 @@ async function main() {
50
50
  view: require('../src/commands/view'),
51
51
  preview: require('../src/commands/preview'),
52
52
  where: require('../src/commands/where'),
53
+ dump: require('../src/commands/dump'),
53
54
  ref: require('../src/commands/ref'),
54
55
  init: require('../src/commands/init'),
55
56
  pull: require('../src/commands/pull'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.8.8",
3
+ "version": "1.9.0",
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": [
@@ -33,6 +33,7 @@
33
33
  "test:cmd:view": "node tests/run-all.js --cmd --command=view",
34
34
  "test:cmd:preview": "node tests/run-all.js --cmd --command=preview",
35
35
  "test:cmd:tree": "node tests/run-all.js --cmd --command=tree",
36
+ "test:cmd:dump": "node tests/run-all.js --cmd --command=dump",
36
37
  "test:cmd:upgrade": "node tests/run-all.js --cmd --command=upgrade",
37
38
  "test:lifecycle": "node tests/run-all.js --lifecycle",
38
39
  "test:pull": "node tests/run-all.js --pull",
@@ -0,0 +1,327 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Dump command - Query short dumps (ST22) from ABAP system
5
+ */
6
+
7
+ // Convert a local date/time in the given IANA timezone to a UTC Date.
8
+ // Uses an iterative approach to reliably handle DST transitions.
9
+ function localToUTC(dateStr, timeStr, timezone) {
10
+ const iso = `${dateStr.slice(0,4)}-${dateStr.slice(4,6)}-${dateStr.slice(6,8)}` +
11
+ `T${timeStr.slice(0,2)}:${timeStr.slice(2,4)}:${timeStr.slice(4,6)}`;
12
+ let candidate = new Date(iso + 'Z');
13
+ for (let i = 0; i < 3; i++) {
14
+ const localStr = candidate.toLocaleString('sv-SE', { timeZone: timezone }).replace(' ', 'T');
15
+ const diff = new Date(iso + 'Z').getTime() - new Date(localStr + 'Z').getTime();
16
+ candidate = new Date(candidate.getTime() + diff);
17
+ }
18
+ return candidate;
19
+ }
20
+
21
+ // Format a UTC Date as a local date/time in the given IANA timezone.
22
+ // Returns { date: 'YYYY-MM-DD', time: 'HH:MM:SS' }.
23
+ function utcToLocal(utcDate, timezone) {
24
+ const local = utcDate.toLocaleString('sv-SE', { timeZone: timezone });
25
+ const [date, time] = local.split(' ');
26
+ return { date, time };
27
+ }
28
+
29
+ // Parse a 14-char UTC timestamp string (YYYYMMDDhhmmss) to a Date.
30
+ function parseUTCTimestamp(ts) {
31
+ const s = String(ts).padStart(14, '0');
32
+ return new Date(`${s.slice(0,4)}-${s.slice(4,6)}-${s.slice(6,8)}T${s.slice(8,10)}:${s.slice(10,12)}:${s.slice(12,14)}Z`);
33
+ }
34
+
35
+ // Format a UTC Date as a 14-char timestamp string (YYYYMMDDhhmmss).
36
+ function toTimestampStr(date) {
37
+ return date.toISOString().replace(/[-T:Z]/g, '').slice(0, 14);
38
+ }
39
+
40
+ // Parse the user's --date argument into { from, to } as YYYYMMDD strings,
41
+ // respecting the given timezone for TODAY/YESTERDAY keywords.
42
+ function parseDateArg(dateStr, timezone) {
43
+ const nowLocal = utcToLocal(new Date(), timezone);
44
+ const todayStr = nowLocal.date.replace(/-/g, '');
45
+
46
+ const upper = dateStr.toUpperCase();
47
+ if (upper === 'TODAY') {
48
+ return { from: todayStr, to: todayStr };
49
+ }
50
+ if (upper === 'YESTERDAY') {
51
+ const d = new Date(nowLocal.date + 'T00:00:00Z');
52
+ d.setUTCDate(d.getUTCDate() - 1);
53
+ const yStr = d.toISOString().slice(0, 10).replace(/-/g, '');
54
+ return { from: yStr, to: yStr };
55
+ }
56
+ if (dateStr.includes('..')) {
57
+ const [a, b] = dateStr.split('..');
58
+ return { from: a.replace(/-/g, ''), to: b.replace(/-/g, '') };
59
+ }
60
+ const d = dateStr.replace(/-/g, '');
61
+ return { from: d, to: d };
62
+ }
63
+
64
+ function parseTimeArg(timeStr) {
65
+ const padTime = (t) => (t.replace(/:/g, '') + '000000').substring(0, 6);
66
+ if (timeStr.includes('..')) {
67
+ const [a, b] = timeStr.split('..');
68
+ return { from: padTime(a), to: padTime(b) };
69
+ }
70
+ const t = padTime(timeStr);
71
+ return { from: t, to: t };
72
+ }
73
+
74
+ // Resolve display date/time for a dump entry, using utc_timestamp when available.
75
+ function resolveDateTime(dump, timezone) {
76
+ const ts = dump.UTC_TIMESTAMP || dump.utc_timestamp;
77
+ if (ts && timezone) {
78
+ const utcDate = parseUTCTimestamp(ts);
79
+ return utcToLocal(utcDate, timezone);
80
+ }
81
+ return {
82
+ date: dump.DATE || dump.date || '',
83
+ time: dump.TIME || dump.time || '',
84
+ };
85
+ }
86
+
87
+ function renderList(dumps, total, limit, timezone) {
88
+ const totalNum = Number(total) || dumps.length;
89
+ const tzLabel = timezone ? ` [${timezone}]` : '';
90
+ console.log(`\n Short Dumps (${totalNum} found)${tzLabel}\n`);
91
+
92
+ if (dumps.length === 0) {
93
+ console.log(' No short dumps found for the given filters.\n');
94
+ return;
95
+ }
96
+
97
+ const W = { num: 3, date: 10, time: 8, user: 12, prog: 30, err: 40 };
98
+ const hdr = ` ${'#'.padStart(W.num)} ${'Date'.padEnd(W.date)} ${'Time'.padEnd(W.time)} ${'User'.padEnd(W.user)} ${'Program'.padEnd(W.prog)} Error`;
99
+ const sep = ' ' + '-'.repeat(W.num + 2 + W.date + 2 + W.time + 2 + W.user + 2 + W.prog + 2 + W.err);
100
+ console.log(hdr);
101
+ console.log(sep);
102
+
103
+ dumps.forEach((d, i) => {
104
+ const { date, time } = resolveDateTime(d, timezone);
105
+ const user = (d.USER || d.user || '').substring(0, W.user);
106
+ const prog = (d.PROGRAM || d.program || '').substring(0, W.prog);
107
+ const err = (d.ERROR || d.error || '');
108
+ console.log(` ${String(i + 1).padStart(W.num)} ${date.padEnd(W.date)} ${time.padEnd(W.time)} ${user.padEnd(W.user)} ${prog.padEnd(W.prog)} ${err}`);
109
+ });
110
+
111
+ console.log('');
112
+ if (totalNum > limit) {
113
+ console.log(` Showing ${dumps.length} of ${totalNum} dumps. Use --limit to see more.`);
114
+ }
115
+ console.log(' Use --detail <number> to see full dump details\n');
116
+ }
117
+
118
+ function renderDetail(dump, timezone) {
119
+ console.log('\n Short Dump Detail\n');
120
+
121
+ const { date, time } = resolveDateTime(dump, timezone);
122
+ const tzLabel = timezone ? ` (${timezone})` : '';
123
+
124
+ const fields = [
125
+ ['Error', dump.ERROR || dump.error || ''],
126
+ ['Date', date + tzLabel],
127
+ ['Time', time],
128
+ ['User', dump.USER || dump.user || ''],
129
+ ['Program', dump.PROGRAM || dump.program || ''],
130
+ ['Object', dump.OBJECT || dump.object || ''],
131
+ ['Package', dump.PACKAGE || dump.package || ''],
132
+ ['Exception', dump.EXCEPTION || dump.exception || ''],
133
+ ];
134
+ fields.forEach(([label, val]) => {
135
+ if (val) {
136
+ console.log(` ${label.padEnd(12)}${val}`);
137
+ }
138
+ });
139
+
140
+ const normalize = (s) => s.replace(/\\n/g, '\n').replace(/\\r/g, '');
141
+ const whatHappened = normalize(dump.WHAT_HAPPENED || dump.what_happened || '');
142
+ const errorAnalysis = normalize(dump.ERROR_ANALYSIS || dump.error_analysis || '');
143
+
144
+ if (whatHappened) {
145
+ console.log('\n What happened:');
146
+ console.log(' ' + '-'.repeat(55));
147
+ console.log(whatHappened.split('\n').map(l => ' ' + l).join('\n'));
148
+ }
149
+ if (errorAnalysis) {
150
+ console.log('\n Error analysis:');
151
+ console.log(' ' + '-'.repeat(55));
152
+ console.log(errorAnalysis.split('\n').map(l => ' ' + l).join('\n'));
153
+ }
154
+
155
+ const stack = dump.CALL_STACK || dump.call_stack || [];
156
+
157
+ // Separate structured frames (have level) from source block (no level, has raw text)
158
+ const frames = stack.filter(f => (f.LEVEL || f.level));
159
+ const srcEntries = stack.filter(f => !(f.LEVEL || f.level) && (f.METHOD || f.method));
160
+
161
+ if (frames.length > 0) {
162
+ console.log('\n Call stack:');
163
+ console.log(' ' + '-'.repeat(55));
164
+ frames.forEach((frame) => {
165
+ const level = frame.LEVEL || frame.level || '';
166
+ const cls = frame.CLASS || frame.class || '';
167
+ const method = frame.METHOD || frame.method || '';
168
+ const program = frame.PROGRAM || frame.program || '';
169
+ const include = frame.INCLUDE || frame.include || '';
170
+ const line = frame.LINE || frame.line || '';
171
+ const location = cls ? `${cls}->${method}` : (method ? `${program} ${method}` : program || include);
172
+ const lineRef = line ? ` (line ${line})` : '';
173
+ console.log(` ${String(level).padStart(3)} ${location}${lineRef}`);
174
+ });
175
+ }
176
+
177
+ if (srcEntries.length > 0) {
178
+ const srcInc = dump.SOURCE_INCLUDE || dump.source_include || '';
179
+ const srcLine = dump.SOURCE_LINE || dump.source_line || 0;
180
+ const heading = srcInc ? ` Source (${srcInc}, line ${srcLine}):` : ' Source:';
181
+ console.log('\n' + heading);
182
+ console.log(' ' + '-'.repeat(55));
183
+ const rawText = srcEntries[0].METHOD || srcEntries[0].method || '';
184
+ console.log(normalize(rawText).split('\n').map(l => ' ' + l).join('\n'));
185
+ }
186
+
187
+ console.log('');
188
+ }
189
+
190
+ module.exports = {
191
+ name: 'dump',
192
+ description: 'Query short dumps (ST22) from ABAP system',
193
+ requiresAbapConfig: true,
194
+ requiresVersionCheck: false,
195
+
196
+ async execute(args, context) {
197
+ const { loadConfig, AbapHttp } = context;
198
+
199
+ const idx = (flag) => args.indexOf(flag);
200
+ const val = (flag) => {
201
+ const i = idx(flag);
202
+ return i !== -1 && i + 1 < args.length ? args[i + 1] : null;
203
+ };
204
+
205
+ const userRaw = val('--user');
206
+ const dateRaw = val('--date');
207
+ const timeRaw = val('--time');
208
+ const programRaw = val('--program');
209
+ const errorRaw = val('--error');
210
+ const limitRaw = val('--limit');
211
+ const detailRaw = val('--detail');
212
+ const timezoneRaw = val('--timezone');
213
+ const jsonOutput = args.includes('--json');
214
+
215
+ const user = userRaw ? userRaw.toUpperCase() : null;
216
+ const program = programRaw ? programRaw.toUpperCase() : null;
217
+ const error = errorRaw ? errorRaw.toUpperCase() : null;
218
+
219
+ // Resolve timezone: explicit flag > system default
220
+ const timezone = timezoneRaw || Intl.DateTimeFormat().resolvedOptions().timeZone;
221
+
222
+ let limit = 20;
223
+ if (limitRaw) {
224
+ const parsed = parseInt(limitRaw, 10);
225
+ if (!isNaN(parsed) && parsed > 0) {
226
+ limit = Math.min(parsed, 100);
227
+ }
228
+ }
229
+
230
+ // Build UTC timestamp range from user's date/time in their timezone
231
+ let tsFrom = null, tsTo = null;
232
+ if (dateRaw) {
233
+ const dates = parseDateArg(dateRaw, timezone);
234
+ const timeF = timeRaw ? parseTimeArg(timeRaw).from : '000000';
235
+ const timeT = timeRaw ? parseTimeArg(timeRaw).to : '235959';
236
+ tsFrom = toTimestampStr(localToUTC(dates.from, timeF, timezone));
237
+ tsTo = toTimestampStr(localToUTC(dates.to, timeT, timezone));
238
+ } else if (timeRaw) {
239
+ // Time filter without date: apply to the default 7-day window
240
+ const now = utcToLocal(new Date(), timezone);
241
+ const today = now.date.replace(/-/g, '');
242
+ const sevenAgo = (() => {
243
+ const d = new Date(now.date + 'T00:00:00Z');
244
+ d.setUTCDate(d.getUTCDate() - 7);
245
+ return d.toISOString().slice(0, 10).replace(/-/g, '');
246
+ })();
247
+ const times = parseTimeArg(timeRaw);
248
+ tsFrom = toTimestampStr(localToUTC(sevenAgo, times.from, timezone));
249
+ tsTo = toTimestampStr(localToUTC(today, times.to, timezone));
250
+ }
251
+
252
+ const detailN = detailRaw ? parseInt(detailRaw, 10) : null;
253
+
254
+ const config = loadConfig();
255
+ const http = new AbapHttp(config);
256
+ const csrfToken = await http.fetchCsrfToken();
257
+
258
+ const buildData = (extra) => {
259
+ const data = { limit };
260
+ if (user) data.user = user;
261
+ if (program) data.program = program;
262
+ if (error) data.error = error;
263
+ if (tsFrom) {
264
+ data.ts_from = tsFrom;
265
+ data.ts_to = tsTo;
266
+ }
267
+ return Object.assign(data, extra);
268
+ };
269
+
270
+ if (detailN) {
271
+ // Two-step: first list (using limit 100 to get full page), then detail
272
+ const listResult = await http.post('/sap/bc/z_abapgit_agent/dump', buildData({ limit: 100 }), { csrfToken });
273
+ const listSuccess = listResult.SUCCESS || listResult.success;
274
+ const listErr = listResult.ERROR || listResult.error;
275
+
276
+ if (!listSuccess || listErr) {
277
+ console.error(`\n Error: ${listErr || 'Failed to query short dumps'}\n`);
278
+ return;
279
+ }
280
+
281
+ const dumps = listResult.DUMPS || listResult.dumps || [];
282
+ if (detailN < 1 || detailN > dumps.length) {
283
+ console.error(`\n Error: Row number ${detailN} not found in results (found ${dumps.length} dump(s))\n`);
284
+ return;
285
+ }
286
+
287
+ const targetId = dumps[detailN - 1].ID || dumps[detailN - 1].id;
288
+ const detailResult = await http.post('/sap/bc/z_abapgit_agent/dump', buildData({ detail: targetId }), { csrfToken });
289
+
290
+ const success = detailResult.SUCCESS || detailResult.success;
291
+ const errMsg = detailResult.ERROR || detailResult.error;
292
+
293
+ if (!success || errMsg) {
294
+ console.error(`\n Error: ${errMsg || 'Failed to load dump detail'}\n`);
295
+ return;
296
+ }
297
+
298
+ if (jsonOutput) {
299
+ console.log(JSON.stringify(detailResult, null, 2));
300
+ return;
301
+ }
302
+
303
+ const detailDumps = detailResult.DUMPS || detailResult.dumps || [];
304
+ renderDetail(detailDumps[0] || {}, timezone);
305
+ return;
306
+ }
307
+
308
+ // List mode
309
+ const result = await http.post('/sap/bc/z_abapgit_agent/dump', buildData({}), { csrfToken });
310
+ const success = result.SUCCESS || result.success;
311
+ const errMsg = result.ERROR || result.error;
312
+
313
+ if (!success || errMsg) {
314
+ console.error(`\n Error: ${errMsg || 'Failed to query short dumps'}\n`);
315
+ return;
316
+ }
317
+
318
+ if (jsonOutput) {
319
+ console.log(JSON.stringify(result, null, 2));
320
+ return;
321
+ }
322
+
323
+ const dumps = result.DUMPS || result.dumps || [];
324
+ const total = result.TOTAL || result.total || dumps.length;
325
+ renderList(dumps, total, limit, timezone);
326
+ }
327
+ };
@@ -58,6 +58,9 @@ Commands:
58
58
  where --objects <obj1>,<obj2>,... [--type <type>] [--limit <n>] [--json]
59
59
  Find where-used list for ABAP objects (classes, interfaces, programs)
60
60
 
61
+ dump [--user <user>] [--date <date>] [--time <HH:MM..HH:MM>] [--timezone <tz>] [--program <prog>] [--error <error>] [--limit <n>] [--detail <n>] [--json]
62
+ Query short dumps (ST22) from ABAP system. Use --detail <n> to view full details.
63
+
61
64
  ref <pattern> [--json]
62
65
  Search ABAP reference repositories for patterns. Requires referenceFolder in .abapGitAgent.
63
66
 
@@ -100,12 +103,14 @@ Examples:
100
103
  abapgit-agent list --package $MY_PACKAGE --type CLAS,INTF # List classes & interfaces
101
104
  abapgit-agent view --objects ZCL_MY_CLASS # View class definition
102
105
  abapgit-agent where --objects ZCL_MY_CLASS # Find where class is used
106
+ abapgit-agent dump --date TODAY # Recent short dumps
107
+ abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
103
108
  abapgit-agent ref "CORRESPONDING" # Search for pattern
104
109
  abapgit-agent ref --topic exceptions # View exceptions topic
105
110
  abapgit-agent health # Health check
106
111
  abapgit-agent status # Configuration status
107
112
 
108
- For more info: https://github.tools.sap/I045696/abapgit-agent
113
+ For more info: https://github.com/SylvosCai/abapgit-agent
109
114
  `);
110
115
  }
111
116
  };
@@ -77,7 +77,7 @@ module.exports = {
77
77
  await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput);
78
78
  },
79
79
 
80
- async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false) {
80
+ async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined) {
81
81
  const TERM_WIDTH = process.stdout.columns || 80;
82
82
 
83
83
  if (!jsonOutput) {
@@ -99,11 +99,15 @@ module.exports = {
99
99
  const csrfToken = await http.fetchCsrfToken();
100
100
 
101
101
  // Prepare request data with git credentials
102
+ // gitCredentials=null means no credentials (public repo); undefined means use config defaults
103
+ const resolvedCredentials = gitCredentials === undefined
104
+ ? { username: config.gitUsername, password: config.gitPassword }
105
+ : gitCredentials;
106
+
102
107
  const data = {
103
108
  url: gitUrl,
104
109
  branch: branch,
105
- username: config.gitUsername,
106
- password: config.gitPassword
110
+ ...(resolvedCredentials ? { username: resolvedCredentials.username, password: resolvedCredentials.password } : {})
107
111
  };
108
112
 
109
113
  // Add files array if specified
@@ -198,9 +202,9 @@ module.exports = {
198
202
  console.log(` Job ID: ${jobId || 'N/A'}`);
199
203
  console.log(` Message: ${message || 'N/A'}`);
200
204
  } else {
201
- console.log(`❌ Pull completed with errors!`);
202
- console.log(` Job ID: ${jobId || 'N/A'}`);
203
- console.log(` Message: ${message || 'N/A'}`);
205
+ console.error(`❌ Pull completed with errors!`);
206
+ console.error(` Job ID: ${jobId || 'N/A'}`);
207
+ console.error(` Message: ${message || 'N/A'}`);
204
208
  }
205
209
 
206
210
  // Display error detail if available
@@ -306,8 +310,18 @@ module.exports = {
306
310
  console.log(`\n❌ Failed Objects Log (${failedCount})`);
307
311
  }
308
312
 
313
+ // Throw if pull was not successful so callers (e.g. upgrade) can detect failure
314
+ if (success !== 'X' && success !== true) {
315
+ const err = new Error(message || 'Pull completed with errors');
316
+ err._isPullError = true;
317
+ throw err;
318
+ }
319
+
309
320
  return result;
310
321
  } catch (error) {
322
+ if (error._isPullError) {
323
+ throw error;
324
+ }
311
325
  console.error(`\n❌ Error: ${error.message}`);
312
326
  process.exit(1);
313
327
  }
@@ -46,10 +46,29 @@ module.exports = {
46
46
  const { apiVersion } = await versionCheck.checkCompatibility(config);
47
47
  abapVersion = apiVersion;
48
48
  } catch (e) {
49
- console.error(`⚠️ Could not fetch ABAP version: ${e.message}`);
49
+ console.error(`⚠️ Could not reach ABAP system: ${e.message}`);
50
50
  }
51
51
  }
52
52
 
53
+ // If ABAP is unreachable and we need to upgrade it, warn and ask whether to continue CLI-only
54
+ if (needsAbapConfig && abapVersion === null && !flags.cliOnly && !flags.abapOnly && !flags.match) {
55
+ console.error('');
56
+ console.error('⚠️ ABAP system is unreachable. Cannot upgrade ABAP backend.');
57
+ if (flags.yes) {
58
+ console.log(' --yes flag set: upgrading CLI only.');
59
+ flags.cliOnly = true;
60
+ } else {
61
+ const proceed = await this.confirmCliOnlyFallback();
62
+ if (!proceed) {
63
+ console.log('Upgrade cancelled.');
64
+ return;
65
+ }
66
+ flags.cliOnly = true;
67
+ }
68
+ console.log(' Run "abapgit-agent upgrade --abap-only" once the system is back.');
69
+ console.log('');
70
+ }
71
+
53
72
  // Get latest version from npm
54
73
  const latestVersion = await versionCheck.getLatestNpmVersion();
55
74
  if (!latestVersion && !flags.version && !flags.match) {
@@ -260,8 +279,8 @@ module.exports = {
260
279
  step++;
261
280
  }
262
281
  if (targets.abapTarget) {
263
- console.log(` ${step}. abapgit-agent pull --branch v${targets.abapTarget}`);
264
- console.log(' (via existing abapGit repository)');
282
+ console.log(` ${step}. abapgit-agent pull --url <agentRepoUrl> --branch v${targets.abapTarget}`);
283
+ console.log(' (agentRepoUrl from .abapGitAgent config or current git remote)');
265
284
  step++;
266
285
  }
267
286
  console.log(` ${step}. Verify versions match`);
@@ -379,7 +398,24 @@ module.exports = {
379
398
  },
380
399
 
381
400
  /**
382
- * Upgrade ABAP backend via pull command
401
+ * Confirm CLI-only fallback when ABAP system is unreachable
402
+ */
403
+ async confirmCliOnlyFallback() {
404
+ return new Promise((resolve) => {
405
+ const rl = readline.createInterface({
406
+ input: process.stdin,
407
+ output: process.stdout
408
+ });
409
+
410
+ rl.question(' Continue with CLI upgrade only? [Y/n] ', (answer) => {
411
+ rl.close();
412
+ const normalized = answer.trim().toLowerCase();
413
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
414
+ });
415
+ });
416
+ },
417
+
418
+ /**
383
419
  */
384
420
  async upgradeAbapBackend(version, transport, context) {
385
421
  console.log(`📦 Upgrading ABAP backend to v${version}...`);
@@ -387,15 +423,20 @@ module.exports = {
387
423
  console.log('');
388
424
 
389
425
  try {
390
- // Build pull command args
391
- const pullArgs = ['--branch', `v${version}`];
392
- if (transport) {
393
- pullArgs.push('--transport', transport);
394
- }
426
+ const { loadConfig } = context;
427
+
428
+ // Determine the abapgit-agent repository URL.
429
+ // Priority:
430
+ // 1. agentRepoUrl from .abapGitAgent config (for forks/mirrors)
431
+ // 2. Default canonical repository
432
+ const config = loadConfig();
433
+ const agentRepoUrl = config.agentRepoUrl || 'https://github.com/SylvosCai/abapgit-agent.git';
395
434
 
396
- // Execute pull command
435
+ // Execute pull command — pass null credentials so the canonical
436
+ // public GitHub repo is not sent the project's SAP internal git credentials.
397
437
  const pullCommand = require('./pull');
398
- await pullCommand.execute(pullArgs, context);
438
+ const { loadConfig: lc, AbapHttp } = context;
439
+ await pullCommand.pull(agentRepoUrl, `v${version}`, null, transport || null, lc, AbapHttp, false, null);
399
440
 
400
441
  console.log('');
401
442
  console.log(`✅ ABAP backend upgraded to v${version}`);
@@ -404,9 +445,13 @@ module.exports = {
404
445
  console.error(`❌ Failed to upgrade ABAP backend: ${error.message}`);
405
446
  console.error('');
406
447
  console.error('This may be due to:');
448
+ console.error(' - ABAP system is unavailable');
407
449
  console.error(' - Git tag not found in repository');
408
450
  console.error(' - ABAP activation errors');
409
451
  console.error(' - Connection issues with ABAP system');
452
+ console.error('');
453
+ console.error('To retry once the system is available:');
454
+ console.error(` abapgit-agent upgrade --abap-only --version ${version}`);
410
455
  process.exit(1);
411
456
  }
412
457
  },
@@ -75,9 +75,9 @@ async function checkCompatibility(config) {
75
75
  const apiVersion = result.version || '1.0.0';
76
76
 
77
77
  if (cliVersion !== apiVersion) {
78
- console.log(`\n⚠️ Version mismatch: CLI ${cliVersion}, ABAP API ${apiVersion}`);
79
- console.log(' Some commands may not work correctly.');
80
- console.log(' Update ABAP code: abapgit-agent pull\n');
78
+ console.error(`\n⚠️ Version mismatch: CLI ${cliVersion}, ABAP API ${apiVersion}`);
79
+ console.error(' Some commands may not work correctly.');
80
+ console.error(' Update ABAP code: abapgit-agent upgrade --match\n');
81
81
  }
82
82
  resolve({ cliVersion, apiVersion, compatible: cliVersion === apiVersion });
83
83
  } catch (e) {
@@ -242,9 +242,9 @@ async function showNewVersionReminder() {
242
242
  const { hasNewVersion, latestVersion, currentVersion } = await checkForNewVersion();
243
243
 
244
244
  if (hasNewVersion) {
245
- console.log('');
246
- console.log(`💡 New version available: ${latestVersion} (current: ${currentVersion})`);
247
- console.log(` Run: abapgit-agent upgrade`);
245
+ console.error('');
246
+ console.error(`💡 New version available: ${latestVersion} (current: ${currentVersion})`);
247
+ console.error(` Run: abapgit-agent upgrade`);
248
248
  }
249
249
  } catch (e) {
250
250
  // Silent - don't interrupt user's workflow