abapgit-agent 1.8.9 → 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 +5 -0
- package/abap/CLAUDE.md +111 -0
- package/bin/abapgit-agent +1 -0
- package/package.json +2 -1
- package/src/commands/dump.js +327 -0
- package/src/commands/help.js +6 -1
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.
|
|
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
|
+
};
|
package/src/commands/help.js
CHANGED
|
@@ -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.
|
|
113
|
+
For more info: https://github.com/SylvosCai/abapgit-agent
|
|
109
114
|
`);
|
|
110
115
|
}
|
|
111
116
|
};
|