abapgit-agent 1.11.1 → 1.11.2
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/.github/copilot-instructions.md +9 -9
- package/abap/CLAUDE.md +48 -539
- package/abap/guidelines/{08_abapgit.md → abapgit.md} +1 -1
- package/abap/guidelines/branch-workflow.md +137 -0
- package/abap/guidelines/cds-testing.md +25 -0
- package/abap/guidelines/{04_cds.md → cds.md} +4 -4
- package/abap/guidelines/{10_common_errors.md → common-errors.md} +3 -3
- package/abap/guidelines/debug-dump.md +33 -0
- package/abap/guidelines/debug-session.md +280 -0
- package/abap/guidelines/index.md +50 -0
- package/abap/guidelines/object-creation.md +51 -0
- package/abap/guidelines/{06_objects.md → objects.md} +2 -2
- package/abap/guidelines/{01_sql.md → sql.md} +2 -2
- package/abap/guidelines/{03_testing.md → testing.md} +3 -3
- package/abap/guidelines/workflow-detailed.md +255 -0
- package/package.json +1 -1
- package/src/commands/debug.js +54 -20
- package/src/commands/inspect.js +5 -3
- package/src/commands/pull.js +4 -1
- package/src/commands/transport.js +3 -1
- package/src/commands/unit.js +10 -10
- package/src/commands/view.js +238 -1
- package/src/utils/abap-http.js +6 -1
- package/src/utils/abap-reference.js +4 -4
- package/src/utils/adt-http.js +6 -1
- package/src/utils/format-error.js +89 -0
- package/abap/guidelines/00_index.md +0 -44
- /package/abap/guidelines/{05_classes.md → classes.md} +0 -0
- /package/abap/guidelines/{02_exceptions.md → exceptions.md} +0 -0
- /package/abap/guidelines/{07_json.md → json.md} +0 -0
- /package/abap/guidelines/{09_unit_testable_code.md → unit-testable-code.md} +0 -0
package/src/commands/view.js
CHANGED
|
@@ -2,6 +2,166 @@
|
|
|
2
2
|
* View command - View ABAP object definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Find the local .clas.abap file for an object by scanning the configured
|
|
10
|
+
* source folder (from the nearest .abapGitAgent config file).
|
|
11
|
+
* Returns the file path if found, null otherwise.
|
|
12
|
+
*/
|
|
13
|
+
function findLocalClassFile(objName) {
|
|
14
|
+
try {
|
|
15
|
+
// Try to read .abapGitAgent to get configured folder
|
|
16
|
+
let folder = null;
|
|
17
|
+
let dir = process.cwd();
|
|
18
|
+
for (let i = 0; i < 5; i++) {
|
|
19
|
+
const cfgPath = path.join(dir, '.abapGitAgent');
|
|
20
|
+
if (fs.existsSync(cfgPath)) {
|
|
21
|
+
try {
|
|
22
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
23
|
+
folder = cfg.folder;
|
|
24
|
+
} catch (e) { /* ignore */ }
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
const parent = path.dirname(dir);
|
|
28
|
+
if (parent === dir) break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Normalise folder to a relative path segment (strip leading/trailing slashes)
|
|
33
|
+
const folderSeg = folder ? folder.replace(/^\/|\/$/g, '') : null;
|
|
34
|
+
const lowerName = objName.toLowerCase();
|
|
35
|
+
const fileName = `${lowerName}.clas.abap`;
|
|
36
|
+
|
|
37
|
+
const candidates = [];
|
|
38
|
+
if (folderSeg) {
|
|
39
|
+
candidates.push(path.join(process.cwd(), folderSeg, fileName));
|
|
40
|
+
}
|
|
41
|
+
candidates.push(path.join(process.cwd(), 'src', fileName));
|
|
42
|
+
candidates.push(path.join(process.cwd(), 'abap', fileName));
|
|
43
|
+
candidates.push(path.join(process.cwd(), fileName));
|
|
44
|
+
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
47
|
+
}
|
|
48
|
+
} catch (e) { /* ignore */ }
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Given a fully assembled class source (array of lines, 1-indexed positions),
|
|
54
|
+
* return a map of { METHODNAME_UPPER: globalLineNumber } where globalLineNumber
|
|
55
|
+
* is the line on which `METHOD <name>.` appears.
|
|
56
|
+
*
|
|
57
|
+
* Matches lines where the first non-blank token is exactly "METHOD" (case-insensitive)
|
|
58
|
+
* to avoid false matches on comments or string literals.
|
|
59
|
+
*/
|
|
60
|
+
function buildMethodLineMap(sourceLines) {
|
|
61
|
+
const map = {};
|
|
62
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
63
|
+
const condensed = sourceLines[i].trimStart();
|
|
64
|
+
if (/^method\s+/i.test(condensed)) {
|
|
65
|
+
// Extract method name: everything between "METHOD " and the next space/period/paren
|
|
66
|
+
const m = condensed.match(/^method\s+([\w~]+)/i);
|
|
67
|
+
if (m) {
|
|
68
|
+
map[m[1].toUpperCase()] = i + 1; // 1-based line number
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return map;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch the assembled class source from ADT.
|
|
77
|
+
* Returns an array of source lines, or null on failure.
|
|
78
|
+
*/
|
|
79
|
+
async function fetchAdtSource(objName, config) {
|
|
80
|
+
try {
|
|
81
|
+
const { AdtHttp } = require('../utils/adt-http');
|
|
82
|
+
const adt = new AdtHttp(config);
|
|
83
|
+
await adt.fetchCsrfToken();
|
|
84
|
+
const lower = objName.toLowerCase();
|
|
85
|
+
const resp = await adt.get(
|
|
86
|
+
`/sap/bc/adt/oo/classes/${lower}/source/main`,
|
|
87
|
+
{ accept: 'text/plain' }
|
|
88
|
+
);
|
|
89
|
+
if (resp && resp.body) {
|
|
90
|
+
return resp.body.split('\n');
|
|
91
|
+
}
|
|
92
|
+
} catch (e) { /* ignore — fall through */ }
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Compute global_start for each CM section in a sections array.
|
|
98
|
+
* Mutates sections in-place, adding a globalStart property.
|
|
99
|
+
*
|
|
100
|
+
* Strategy:
|
|
101
|
+
* 1. Try local .clas.abap file → build method line map
|
|
102
|
+
* 2. Fall back to ADT source fetch → build method line map
|
|
103
|
+
* 3. If neither works, leave globalStart = 0 (unknown)
|
|
104
|
+
*
|
|
105
|
+
* For non-CM sections (CU, CO, CP, CCDEF, CCIMP, CCAU) globalStart is also
|
|
106
|
+
* set from the source map for sections that have a unique recognisable first line,
|
|
107
|
+
* but for simplicity we set it to 0 for non-CM sections (they use section-local
|
|
108
|
+
* line numbers already).
|
|
109
|
+
*/
|
|
110
|
+
async function computeGlobalStarts(objName, sections, config) {
|
|
111
|
+
// Only CM sections need global_start for breakpoints
|
|
112
|
+
const cmSections = sections.filter(s => {
|
|
113
|
+
const suffix = s.SUFFIX || s.suffix || '';
|
|
114
|
+
const methodName = s.METHOD_NAME || s.method_name || '';
|
|
115
|
+
return suffix.startsWith('CM') && methodName;
|
|
116
|
+
});
|
|
117
|
+
if (cmSections.length === 0) return;
|
|
118
|
+
|
|
119
|
+
let sourceLines = null;
|
|
120
|
+
|
|
121
|
+
// Try local file first
|
|
122
|
+
const localFile = findLocalClassFile(objName);
|
|
123
|
+
if (localFile) {
|
|
124
|
+
try {
|
|
125
|
+
sourceLines = fs.readFileSync(localFile, 'utf8').split('\n');
|
|
126
|
+
} catch (e) { /* ignore */ }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Fall back to ADT source fetch
|
|
130
|
+
if (!sourceLines) {
|
|
131
|
+
sourceLines = await fetchAdtSource(objName, config);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!sourceLines) return;
|
|
135
|
+
|
|
136
|
+
const methodLineMap = buildMethodLineMap(sourceLines);
|
|
137
|
+
|
|
138
|
+
for (const section of cmSections) {
|
|
139
|
+
const methodName = (section.METHOD_NAME || section.method_name || '').toUpperCase();
|
|
140
|
+
if (methodLineMap[methodName] !== undefined) {
|
|
141
|
+
section.globalStart = methodLineMap[methodName];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Given the lines of a CM method section, return the 0-based index of the
|
|
148
|
+
* first "executable" line — i.e. skip METHOD, blank lines, and pure
|
|
149
|
+
* declaration lines (DATA/FINAL/TYPES/CONSTANTS/CLASS-DATA).
|
|
150
|
+
* Returns 0 if no better line is found (falls back to METHOD statement).
|
|
151
|
+
*/
|
|
152
|
+
function findFirstExecutableLine(lines) {
|
|
153
|
+
const declPattern = /^\s*(data|final|types|constants|class-data)[\s:]/i;
|
|
154
|
+
const methodPattern = /^\s*method\s+/i;
|
|
155
|
+
for (let i = 0; i < lines.length; i++) {
|
|
156
|
+
const trimmed = lines[i].trim();
|
|
157
|
+
if (!trimmed) continue; // blank line
|
|
158
|
+
if (methodPattern.test(trimmed)) continue; // METHOD statement itself
|
|
159
|
+
if (declPattern.test(trimmed)) continue; // declaration
|
|
160
|
+
return i;
|
|
161
|
+
}
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
5
165
|
module.exports = {
|
|
6
166
|
name: 'view',
|
|
7
167
|
description: 'View ABAP object definitions from ABAP system',
|
|
@@ -24,6 +184,8 @@ module.exports = {
|
|
|
24
184
|
const typeArg = args.indexOf('--type');
|
|
25
185
|
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
26
186
|
const jsonOutput = args.includes('--json');
|
|
187
|
+
const fullMode = args.includes('--full');
|
|
188
|
+
const linesMode = args.includes('--lines');
|
|
27
189
|
|
|
28
190
|
console.log(`\n Viewing ${objects.length} object(s)`);
|
|
29
191
|
|
|
@@ -39,6 +201,10 @@ module.exports = {
|
|
|
39
201
|
data.type = type;
|
|
40
202
|
}
|
|
41
203
|
|
|
204
|
+
if (fullMode) {
|
|
205
|
+
data.full = true;
|
|
206
|
+
}
|
|
207
|
+
|
|
42
208
|
const result = await http.post('/sap/bc/z_abapgit_agent/view', data, { csrfToken });
|
|
43
209
|
|
|
44
210
|
// Handle uppercase keys from ABAP
|
|
@@ -52,6 +218,17 @@ module.exports = {
|
|
|
52
218
|
return;
|
|
53
219
|
}
|
|
54
220
|
|
|
221
|
+
// In full+lines mode, compute global line numbers client-side before rendering
|
|
222
|
+
if (fullMode && linesMode) {
|
|
223
|
+
for (const obj of viewObjects) {
|
|
224
|
+
const objName = obj.NAME || obj.name || '';
|
|
225
|
+
const sections = obj.SECTIONS || obj.sections || [];
|
|
226
|
+
if (sections.length > 0 && objName) {
|
|
227
|
+
await computeGlobalStarts(objName, sections, config);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
55
232
|
if (jsonOutput) {
|
|
56
233
|
console.log(JSON.stringify(result, null, 2));
|
|
57
234
|
} else {
|
|
@@ -86,7 +263,67 @@ module.exports = {
|
|
|
86
263
|
|
|
87
264
|
// Display source code for classes, interfaces, CDS views, programs/source includes, and STOB
|
|
88
265
|
const source = obj.SOURCE || obj.source || '';
|
|
89
|
-
|
|
266
|
+
const sections = obj.SECTIONS || obj.sections || [];
|
|
267
|
+
|
|
268
|
+
if (sections.length > 0) {
|
|
269
|
+
// --full mode: render all sections.
|
|
270
|
+
// --full --lines adds dual line numbers per line for debugging.
|
|
271
|
+
console.log('');
|
|
272
|
+
for (const section of sections) {
|
|
273
|
+
const suffix = section.SUFFIX || section.suffix || '';
|
|
274
|
+
const methodName = section.METHOD_NAME || section.method_name || '';
|
|
275
|
+
const file = section.FILE || section.file || '';
|
|
276
|
+
const lines = section.LINES || section.lines || [];
|
|
277
|
+
const isCmSection = suffix.startsWith('CM') && methodName;
|
|
278
|
+
|
|
279
|
+
if (linesMode) {
|
|
280
|
+
// --full --lines: dual line numbers (G [N]) for debugging
|
|
281
|
+
const globalStart = section.globalStart || 0;
|
|
282
|
+
|
|
283
|
+
if (isCmSection) {
|
|
284
|
+
let bpHint;
|
|
285
|
+
if (globalStart) {
|
|
286
|
+
const execOffset = findFirstExecutableLine(lines);
|
|
287
|
+
const execLine = globalStart + execOffset;
|
|
288
|
+
bpHint = `debug set --objects ${objName}:${execLine}`;
|
|
289
|
+
} else {
|
|
290
|
+
bpHint = `debug set --objects ${objName}:<global_line>`;
|
|
291
|
+
}
|
|
292
|
+
console.log(` * ---- Method: ${methodName} (${suffix}) — breakpoint: ${bpHint} ----`);
|
|
293
|
+
} else if (file) {
|
|
294
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
295
|
+
} else if (suffix) {
|
|
296
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (${suffix}) ----`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let includeRelLine = 0;
|
|
300
|
+
for (const codeLine of lines) {
|
|
301
|
+
includeRelLine++;
|
|
302
|
+
const globalLine = globalStart ? globalStart + includeRelLine - 1 : 0;
|
|
303
|
+
if (isCmSection) {
|
|
304
|
+
const gStr = globalLine ? String(globalLine).padStart(4) : ' ';
|
|
305
|
+
const iStr = String(includeRelLine).padStart(3);
|
|
306
|
+
console.log(` ${gStr} [${iStr}] ${codeLine}`);
|
|
307
|
+
} else {
|
|
308
|
+
const lStr = globalLine ? String(globalLine).padStart(4) : String(includeRelLine).padStart(4);
|
|
309
|
+
console.log(` ${lStr} ${codeLine}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
// --full (no --lines): clean source, no line numbers
|
|
314
|
+
if (isCmSection) {
|
|
315
|
+
console.log(` * ---- Method: ${methodName} (${suffix}) ----`);
|
|
316
|
+
} else if (file) {
|
|
317
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
|
|
318
|
+
} else if (suffix) {
|
|
319
|
+
console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (${suffix}) ----`);
|
|
320
|
+
}
|
|
321
|
+
for (const codeLine of lines) {
|
|
322
|
+
console.log(` ${codeLine}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} else if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View' || objType === 'PROG' || objType === 'Program' || objType === 'STOB' || objType === 'Structured Object')) {
|
|
90
327
|
console.log('');
|
|
91
328
|
// Replace escaped newlines with actual newlines and display
|
|
92
329
|
const displaySource = source.replace(/\\n/g, '\n');
|
package/src/utils/abap-http.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const https = require('https');
|
|
5
5
|
const http = require('http');
|
|
6
|
+
const { extractBodyDetail } = require('./format-error');
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const path = require('path');
|
|
8
9
|
const os = require('os');
|
|
@@ -269,9 +270,13 @@ class AbapHttp {
|
|
|
269
270
|
let body = '';
|
|
270
271
|
res.on('data', chunk => body += chunk);
|
|
271
272
|
res.on('end', () => {
|
|
273
|
+
const detail = extractBodyDetail(body);
|
|
274
|
+
const message = detail
|
|
275
|
+
? `(HTTP ${res.statusCode}) ${detail}`
|
|
276
|
+
: `(HTTP ${res.statusCode}) ${res.statusMessage || 'Internal Server Error'}`;
|
|
272
277
|
reject({
|
|
273
278
|
statusCode: res.statusCode,
|
|
274
|
-
message
|
|
279
|
+
message,
|
|
275
280
|
body: body
|
|
276
281
|
});
|
|
277
282
|
});
|
|
@@ -508,10 +508,10 @@ async function getTopic(topic) {
|
|
|
508
508
|
if (guidelinesDir) {
|
|
509
509
|
// Map topic to local guideline file
|
|
510
510
|
const guidelineMap = {
|
|
511
|
-
'abapgit': '
|
|
512
|
-
'xml': '
|
|
513
|
-
'objects': '
|
|
514
|
-
'naming': '
|
|
511
|
+
'abapgit': 'abapgit.md',
|
|
512
|
+
'xml': 'objects.md',
|
|
513
|
+
'objects': 'objects.md',
|
|
514
|
+
'naming': 'objects.md'
|
|
515
515
|
};
|
|
516
516
|
|
|
517
517
|
const guidelineFile = guidelineMap[topicLower];
|
package/src/utils/adt-http.js
CHANGED
|
@@ -8,6 +8,7 @@ const https = require('https');
|
|
|
8
8
|
const http = require('http');
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
+
const { extractBodyDetail } = require('./format-error');
|
|
11
12
|
const os = require('os');
|
|
12
13
|
const crypto = require('crypto');
|
|
13
14
|
|
|
@@ -194,7 +195,11 @@ class AdtHttp {
|
|
|
194
195
|
let respBody = '';
|
|
195
196
|
res.on('data', chunk => respBody += chunk);
|
|
196
197
|
res.on('end', () => {
|
|
197
|
-
|
|
198
|
+
const detail = extractBodyDetail(respBody);
|
|
199
|
+
const message = detail
|
|
200
|
+
? `(HTTP ${res.statusCode}) ${detail}`
|
|
201
|
+
: `(HTTP ${res.statusCode}) ${res.statusMessage || 'Internal Server Error'}`;
|
|
202
|
+
reject({ statusCode: res.statusCode, message, body: respBody });
|
|
198
203
|
});
|
|
199
204
|
return;
|
|
200
205
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP error formatting utilities.
|
|
3
|
+
*
|
|
4
|
+
* When ABAP returns HTTP 4xx/5xx the abap-http.js layer captures the raw
|
|
5
|
+
* response body in `error.body`. This module extracts a human-readable
|
|
6
|
+
* detail line from that body and provides a consistent display helper used
|
|
7
|
+
* by all command modules.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract a short, human-readable detail string from an HTTP error's response
|
|
12
|
+
* body. Handles the three common SAP response shapes:
|
|
13
|
+
* - JSON with a `message` or `error` field (our own ABAP handlers)
|
|
14
|
+
* - SAP ICF XML/HTML error pages (raw ICM 500 page)
|
|
15
|
+
* - Plain text
|
|
16
|
+
*
|
|
17
|
+
* @param {string|object} body - raw response body (string or already-parsed object)
|
|
18
|
+
* @returns {string|null} - detail text, or null if nothing useful found
|
|
19
|
+
*/
|
|
20
|
+
function extractBodyDetail(body) {
|
|
21
|
+
if (!body) return null;
|
|
22
|
+
|
|
23
|
+
// Already a parsed object (e.g. CSRF error shape)
|
|
24
|
+
if (typeof body === 'object') {
|
|
25
|
+
return body.message || body.error || body.MESSAGE || body.ERROR || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Try to parse as JSON first
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(body);
|
|
31
|
+
return parsed.message || parsed.error || parsed.MESSAGE || parsed.ERROR || null;
|
|
32
|
+
} catch (_) {
|
|
33
|
+
// Not JSON — fall through
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// SAP ICF HTML/XML error page: grab the first <p> or <title> content
|
|
37
|
+
const titleMatch = body.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
38
|
+
if (titleMatch) return titleMatch[1].trim();
|
|
39
|
+
|
|
40
|
+
const pMatch = body.match(/<p[^>]*>([^<]{10,})<\/p>/i);
|
|
41
|
+
if (pMatch) return pMatch[1].trim();
|
|
42
|
+
|
|
43
|
+
// Plain text — return first non-empty line (up to 200 chars)
|
|
44
|
+
const firstLine = body.split('\n').map(l => l.trim()).find(l => l.length > 0);
|
|
45
|
+
if (firstLine) return firstLine.substring(0, 200);
|
|
46
|
+
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a display message for an HTTP error, including body detail when
|
|
52
|
+
* available.
|
|
53
|
+
*
|
|
54
|
+
* @param {Error|object} error - error object from abap-http.js
|
|
55
|
+
* @returns {string} - formatted message for console output
|
|
56
|
+
*/
|
|
57
|
+
function formatHttpError(error) {
|
|
58
|
+
const base = error.message || String(error);
|
|
59
|
+
const detail = extractBodyDetail(error.body);
|
|
60
|
+
if (detail && detail !== base) {
|
|
61
|
+
return `${base}\n Detail: ${detail}`;
|
|
62
|
+
}
|
|
63
|
+
return base;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Print a formatted HTTP error to stderr.
|
|
68
|
+
* Optionally dump the full raw body when verbose mode is active.
|
|
69
|
+
*
|
|
70
|
+
* @param {Error|object} error - error from abap-http.js
|
|
71
|
+
* @param {object} [opts] - options
|
|
72
|
+
* @param {boolean} [opts.verbose=false] - dump full raw body
|
|
73
|
+
* @param {string} [opts.prefix='❌ Error'] - prefix for first line
|
|
74
|
+
*/
|
|
75
|
+
function printHttpError(error, { verbose = false, prefix = '❌ Error' } = {}) {
|
|
76
|
+
const msg = formatHttpError(error);
|
|
77
|
+
console.error(`\n${prefix}: ${msg}`);
|
|
78
|
+
|
|
79
|
+
if (verbose && error.body) {
|
|
80
|
+
console.error('\n--- Raw response body ---');
|
|
81
|
+
const raw = typeof error.body === 'object'
|
|
82
|
+
? JSON.stringify(error.body, null, 2)
|
|
83
|
+
: String(error.body);
|
|
84
|
+
console.error(raw);
|
|
85
|
+
console.error('--- End of response body ---');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { formatHttpError, printHttpError, extractBodyDetail };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: default
|
|
3
|
-
title: Overview
|
|
4
|
-
nav_order: 1
|
|
5
|
-
parent: ABAP Coding Guidelines
|
|
6
|
-
grand_parent: ABAP Development
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# ABAP Coding Guidelines Index
|
|
10
|
-
|
|
11
|
-
This folder contains detailed ABAP coding guidelines that can be searched using the `ref` command.
|
|
12
|
-
|
|
13
|
-
## Guidelines Available
|
|
14
|
-
|
|
15
|
-
| File | Topic |
|
|
16
|
-
|------|-------|
|
|
17
|
-
| `01_sql.md` | ABAP SQL Best Practices |
|
|
18
|
-
| `02_exceptions.md` | Exception Handling |
|
|
19
|
-
| `03_testing.md` | Unit Testing (including CDS) |
|
|
20
|
-
| `04_cds.md` | CDS Views |
|
|
21
|
-
| `05_classes.md` | ABAP Classes and Objects |
|
|
22
|
-
| `06_objects.md` | Object Naming Conventions |
|
|
23
|
-
| `07_json.md` | JSON Handling |
|
|
24
|
-
| `08_abapgit.md` | abapGit XML Metadata Templates |
|
|
25
|
-
| `09_unit_testable_code.md` | Unit Testable Code Guidelines (Dependency Injection) |
|
|
26
|
-
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
These guidelines are automatically searched by the `ref` command:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
# Search across all guidelines
|
|
33
|
-
abapgit-agent ref "CORRESPONDING"
|
|
34
|
-
|
|
35
|
-
# List all topics
|
|
36
|
-
abapgit-agent ref --list-topics
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Adding Custom Guidelines
|
|
40
|
-
|
|
41
|
-
To add your own guidelines:
|
|
42
|
-
1. Create a new `.md` file in this folder
|
|
43
|
-
2. Follow the naming convention: `XX_name.md`
|
|
44
|
-
3. Export to reference folder: `abapgit-agent ref export`
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|