abapgit-agent 1.2.0 → 1.4.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/API.md +440 -1
- package/CLAUDE.md +354 -16
- package/INSTALL.md +6 -11
- package/README.md +10 -0
- package/RELEASE_NOTES.md +57 -0
- package/abap/CLAUDE.md +340 -0
- package/abap/copilot-instructions.md +28 -0
- package/abap/zcl_abgagt_agent.clas.abap +2 -2
- package/abap/zcl_abgagt_cmd_factory.clas.abap +1 -0
- package/abap/zcl_abgagt_command_inspect.clas.abap +255 -36
- package/abap/zcl_abgagt_command_preview.clas.abap +386 -0
- package/abap/zcl_abgagt_command_preview.clas.xml +15 -0
- package/abap/zcl_abgagt_command_view.clas.abap +3 -1
- package/abap/zcl_abgagt_resource_preview.clas.abap +67 -0
- package/abap/zcl_abgagt_resource_preview.clas.xml +15 -0
- package/abap/zcl_abgagt_rest_handler.clas.abap +1 -0
- package/abap/zcl_abgagt_util.clas.abap +2 -2
- package/abap/zcl_abgagt_viewer_ddls.clas.abap +83 -0
- package/abap/zcl_abgagt_viewer_ddls.clas.xml +15 -0
- package/abap/zcl_abgagt_viewer_ttyp.clas.abap +93 -0
- package/abap/zcl_abgagt_viewer_ttyp.clas.xml +15 -0
- package/abap/zif_abgagt_command.intf.abap +2 -1
- package/abap/zif_abgagt_viewer.intf.abap +2 -1
- package/bin/abapgit-agent +447 -40
- package/docs/commands.md +5 -0
- package/docs/preview-command.md +528 -0
- package/docs/view-command.md +94 -2
- package/package.json +1 -1
- package/src/abap-client.js +18 -0
- package/src/agent.js +19 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
*"*"use source
|
|
2
|
+
*"*"Local Interface:
|
|
3
|
+
*"**********************************************************************
|
|
4
|
+
CLASS zcl_abgagt_viewer_ttyp DEFINITION PUBLIC FINAL CREATE PUBLIC.
|
|
5
|
+
|
|
6
|
+
PUBLIC SECTION.
|
|
7
|
+
INTERFACES zif_abgagt_viewer.
|
|
8
|
+
|
|
9
|
+
ENDCLASS.
|
|
10
|
+
|
|
11
|
+
CLASS zcl_abgagt_viewer_ttyp IMPLEMENTATION.
|
|
12
|
+
|
|
13
|
+
METHOD zif_abgagt_viewer~get_info.
|
|
14
|
+
DATA: lv_obj_name TYPE tadir-obj_name,
|
|
15
|
+
lv_devclass TYPE tadir-devclass,
|
|
16
|
+
lv_linetype TYPE string,
|
|
17
|
+
lv_tabprottype TYPE string,
|
|
18
|
+
lv_keydef TYPE string,
|
|
19
|
+
lv_access_mode TYPE string,
|
|
20
|
+
lv_key_definition TYPE string.
|
|
21
|
+
|
|
22
|
+
rs_info-name = iv_name.
|
|
23
|
+
rs_info-type = 'TTYP'.
|
|
24
|
+
rs_info-type_text = 'Table Type'.
|
|
25
|
+
|
|
26
|
+
" Get package from TADIR
|
|
27
|
+
SELECT SINGLE obj_name devclass FROM tadir
|
|
28
|
+
INTO (lv_obj_name, lv_devclass)
|
|
29
|
+
WHERE obj_name = iv_name
|
|
30
|
+
AND object = 'TTYP'.
|
|
31
|
+
IF sy-subrc = 0.
|
|
32
|
+
rs_info-description = |Table Type { iv_name } in { lv_devclass }|.
|
|
33
|
+
ELSE.
|
|
34
|
+
rs_info-not_found = abap_true.
|
|
35
|
+
ENDIF.
|
|
36
|
+
|
|
37
|
+
" Get TTYP details from DD40L
|
|
38
|
+
SELECT SINGLE rowtype accessmode keydef FROM dd40l
|
|
39
|
+
INTO (lv_linetype, lv_tabprottype, lv_keydef)
|
|
40
|
+
WHERE typename = iv_name
|
|
41
|
+
AND as4local = 'A'.
|
|
42
|
+
|
|
43
|
+
" Build components table with TTYP details
|
|
44
|
+
IF lv_linetype IS NOT INITIAL.
|
|
45
|
+
APPEND VALUE #(
|
|
46
|
+
field = 'LINE_TYPE'
|
|
47
|
+
key = abap_false
|
|
48
|
+
type = 'CHAR'
|
|
49
|
+
length = 30
|
|
50
|
+
dataelement = ''
|
|
51
|
+
description = |Line Type: { lv_linetype }|
|
|
52
|
+
) TO rs_info-components.
|
|
53
|
+
ENDIF.
|
|
54
|
+
|
|
55
|
+
" Convert access mode to text
|
|
56
|
+
CASE lv_tabprottype.
|
|
57
|
+
WHEN 'T'. lv_access_mode = 'STANDARD'.
|
|
58
|
+
WHEN 'S'. lv_access_mode = 'SORTED'.
|
|
59
|
+
WHEN 'H'. lv_access_mode = 'HASHED'.
|
|
60
|
+
WHEN OTHERS. lv_access_mode = lv_tabprottype.
|
|
61
|
+
ENDCASE.
|
|
62
|
+
|
|
63
|
+
IF lv_access_mode IS NOT INITIAL.
|
|
64
|
+
APPEND VALUE #(
|
|
65
|
+
field = 'ACCESS_MODE'
|
|
66
|
+
key = abap_false
|
|
67
|
+
type = 'CHAR'
|
|
68
|
+
length = 10
|
|
69
|
+
dataelement = ''
|
|
70
|
+
description = |Access Mode: { lv_access_mode }|
|
|
71
|
+
) TO rs_info-components.
|
|
72
|
+
ENDIF.
|
|
73
|
+
|
|
74
|
+
" Convert key definition to text
|
|
75
|
+
CASE lv_keydef.
|
|
76
|
+
WHEN 'D'. lv_key_definition = 'WITH KEY'.
|
|
77
|
+
WHEN 'N'. lv_key_definition = 'NO KEY'.
|
|
78
|
+
WHEN OTHERS. lv_key_definition = lv_keydef.
|
|
79
|
+
ENDCASE.
|
|
80
|
+
|
|
81
|
+
IF lv_key_definition IS NOT INITIAL.
|
|
82
|
+
APPEND VALUE #(
|
|
83
|
+
field = 'KEY_DEFINITION'
|
|
84
|
+
key = abap_false
|
|
85
|
+
type = 'CHAR'
|
|
86
|
+
length = 10
|
|
87
|
+
dataelement = ''
|
|
88
|
+
description = |Key Definition: { lv_key_definition }|
|
|
89
|
+
) TO rs_info-components.
|
|
90
|
+
ENDIF.
|
|
91
|
+
ENDMETHOD.
|
|
92
|
+
|
|
93
|
+
ENDCLASS.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
|
|
3
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
4
|
+
<asx:values>
|
|
5
|
+
<VSEOCLASS>
|
|
6
|
+
<CLSNAME>ZCL_ABGAGT_VIEWER_TTYP</CLSNAME>
|
|
7
|
+
<LANGU>E</LANGU>
|
|
8
|
+
<DESCRIPT>Viewer for ABAP Table Types</DESCRIPT>
|
|
9
|
+
<EXPOSURE>2</EXPOSURE>
|
|
10
|
+
<STATE>1</STATE>
|
|
11
|
+
<UNICODE>X</UNICODE>
|
|
12
|
+
</VSEOCLASS>
|
|
13
|
+
</asx:values>
|
|
14
|
+
</asx:abap>
|
|
15
|
+
</abapGit>
|
|
@@ -9,7 +9,8 @@ INTERFACE zif_abgagt_command PUBLIC.
|
|
|
9
9
|
gc_create TYPE string VALUE 'CREATE',
|
|
10
10
|
gc_import TYPE string VALUE 'IMPORT',
|
|
11
11
|
gc_tree TYPE string VALUE 'TREE',
|
|
12
|
-
gc_view TYPE string VALUE 'VIEW'
|
|
12
|
+
gc_view TYPE string VALUE 'VIEW',
|
|
13
|
+
gc_preview TYPE string VALUE 'PREVIEW'.
|
|
13
14
|
|
|
14
15
|
" Get command name
|
|
15
16
|
METHODS get_name
|
|
@@ -6,6 +6,7 @@ INTERFACE zif_abgagt_viewer PUBLIC.
|
|
|
6
6
|
" @parameter rs_info | Object information structure
|
|
7
7
|
METHODS get_info
|
|
8
8
|
IMPORTING iv_name TYPE string
|
|
9
|
-
RETURNING VALUE(rs_info) TYPE zcl_abgagt_command_view=>ty_view_object
|
|
9
|
+
RETURNING VALUE(rs_info) TYPE zcl_abgagt_command_view=>ty_view_object
|
|
10
|
+
RAISING cx_dd_ddl_read.
|
|
10
11
|
|
|
11
12
|
ENDINTERFACE.
|
package/bin/abapgit-agent
CHANGED
|
@@ -309,33 +309,70 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
|
|
|
309
309
|
|
|
310
310
|
const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
|
|
311
311
|
|
|
312
|
-
// Handle
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
// Handle new table-based result format (array) or old single result
|
|
313
|
+
let results = [];
|
|
314
|
+
if (Array.isArray(result)) {
|
|
315
|
+
results = result;
|
|
316
|
+
} else {
|
|
317
|
+
// Convert old single result to array format for compatibility
|
|
318
|
+
results = [{
|
|
319
|
+
OBJECT_TYPE: 'UNKNOWN',
|
|
320
|
+
OBJECT_NAME: pathModule.basename(sourceFile),
|
|
321
|
+
SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
|
|
322
|
+
ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
|
|
323
|
+
ERRORS: result.ERRORS || result.errors || [],
|
|
324
|
+
WARNINGS: result.warnings || []
|
|
325
|
+
}];
|
|
326
|
+
}
|
|
316
327
|
|
|
317
|
-
|
|
328
|
+
// Process each result
|
|
329
|
+
let hasErrors = false;
|
|
330
|
+
for (const res of results) {
|
|
331
|
+
// Handle both uppercase and lowercase keys
|
|
332
|
+
const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
|
|
333
|
+
const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
|
|
334
|
+
const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
|
|
335
|
+
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
336
|
+
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
337
|
+
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
338
|
+
|
|
339
|
+
console.log('\n');
|
|
340
|
+
|
|
341
|
+
if (errorCount > 0 || warnings.length > 0) {
|
|
342
|
+
if (errorCount > 0) {
|
|
343
|
+
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
|
|
346
|
+
}
|
|
347
|
+
console.log('\nErrors:');
|
|
348
|
+
console.log('─'.repeat(60));
|
|
318
349
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
350
|
+
for (const err of errors) {
|
|
351
|
+
const line = err.LINE || err.line || '?';
|
|
352
|
+
const column = err.COLUMN || err.column || '?';
|
|
353
|
+
const text = err.TEXT || err.text || 'Unknown error';
|
|
323
354
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
355
|
+
console.log(` Line ${line}, Column ${column}:`);
|
|
356
|
+
console.log(` ${text}`);
|
|
357
|
+
console.log('');
|
|
358
|
+
}
|
|
328
359
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
360
|
+
// Show warnings if any
|
|
361
|
+
if (warnings.length > 0) {
|
|
362
|
+
console.log('Warnings:');
|
|
363
|
+
console.log('─'.repeat(60));
|
|
364
|
+
for (const warn of warnings) {
|
|
365
|
+
const line = warn.LINE || warn.line || '?';
|
|
366
|
+
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
367
|
+
console.log(` Line ${line}: ${text}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
hasErrors = true;
|
|
371
|
+
} else if (success === true || success === 'X') {
|
|
372
|
+
console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
|
|
373
|
+
} else {
|
|
374
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
|
|
332
375
|
}
|
|
333
|
-
} else if (success === 'X' || success === true) {
|
|
334
|
-
console.log(`✅ ${pathModule.basename(sourceFile)} - Syntax check passed (0 errors)`);
|
|
335
|
-
} else if (success === false) {
|
|
336
|
-
console.log(`❌ ${pathModule.basename(sourceFile)} - Syntax check failed`);
|
|
337
|
-
} else {
|
|
338
|
-
console.log(`⚠️ Syntax check returned unexpected status: ${success}`);
|
|
339
376
|
}
|
|
340
377
|
|
|
341
378
|
return result;
|
|
@@ -345,6 +382,95 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
|
|
|
345
382
|
}
|
|
346
383
|
}
|
|
347
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Inspect all files in one request
|
|
387
|
+
*/
|
|
388
|
+
async function inspectAllFiles(files, csrfToken, config) {
|
|
389
|
+
// Convert files to uppercase names (same as syntaxCheckSource does)
|
|
390
|
+
const fileNames = files.map(f => {
|
|
391
|
+
const baseName = pathModule.basename(f).toUpperCase();
|
|
392
|
+
return baseName;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
// Send all files in one request
|
|
397
|
+
const data = {
|
|
398
|
+
files: fileNames
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
|
|
402
|
+
|
|
403
|
+
// Handle both table result and old single result
|
|
404
|
+
let results = [];
|
|
405
|
+
if (Array.isArray(result)) {
|
|
406
|
+
results = result;
|
|
407
|
+
} else {
|
|
408
|
+
// Convert single result to array format
|
|
409
|
+
results = [{
|
|
410
|
+
OBJECT_TYPE: 'UNKNOWN',
|
|
411
|
+
OBJECT_NAME: files.join(', '),
|
|
412
|
+
SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
|
|
413
|
+
ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
|
|
414
|
+
ERRORS: result.ERRORS || result.errors || [],
|
|
415
|
+
WARNINGS: result.warnings || []
|
|
416
|
+
}];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return results;
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.error(`\n Error: ${error.message}`);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Process a single inspect result
|
|
428
|
+
*/
|
|
429
|
+
async function processInspectResult(res) {
|
|
430
|
+
// Handle both uppercase and lowercase keys
|
|
431
|
+
const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
|
|
432
|
+
const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
|
|
433
|
+
const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
|
|
434
|
+
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
435
|
+
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
436
|
+
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
437
|
+
|
|
438
|
+
if (errorCount > 0 || warnings.length > 0) {
|
|
439
|
+
if (errorCount > 0) {
|
|
440
|
+
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
441
|
+
} else {
|
|
442
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${warnings.length}):`);
|
|
443
|
+
}
|
|
444
|
+
console.log('\nErrors:');
|
|
445
|
+
console.log('─'.repeat(60));
|
|
446
|
+
|
|
447
|
+
for (const err of errors) {
|
|
448
|
+
const line = err.LINE || err.line || '?';
|
|
449
|
+
const column = err.COLUMN || err.column || '?';
|
|
450
|
+
const text = err.TEXT || err.text || 'Unknown error';
|
|
451
|
+
|
|
452
|
+
console.log(` Line ${line}, Column ${column}:`);
|
|
453
|
+
console.log(` ${text}`);
|
|
454
|
+
console.log('');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Show warnings if any
|
|
458
|
+
if (warnings.length > 0) {
|
|
459
|
+
console.log('Warnings:');
|
|
460
|
+
console.log('─'.repeat(60));
|
|
461
|
+
for (const warn of warnings) {
|
|
462
|
+
const line = warn.LINE || warn.line || '?';
|
|
463
|
+
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
464
|
+
console.log(` Line ${line}: ${text}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else if (success === true || success === 'X') {
|
|
468
|
+
console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
|
|
469
|
+
} else {
|
|
470
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
348
474
|
/**
|
|
349
475
|
* Run unit tests for package or objects
|
|
350
476
|
*/
|
|
@@ -691,7 +817,46 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
|
|
|
691
817
|
'W': '⚠️', // Warning
|
|
692
818
|
'A': '🛑' // Abort
|
|
693
819
|
};
|
|
694
|
-
return icons[type] || '
|
|
820
|
+
return icons[type] || '';
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
|
|
824
|
+
const calcWidth = (str) => {
|
|
825
|
+
if (!str) return 0;
|
|
826
|
+
let width = 0;
|
|
827
|
+
let i = 0;
|
|
828
|
+
while (i < str.length) {
|
|
829
|
+
const code = str.codePointAt(i);
|
|
830
|
+
if (!code) break;
|
|
831
|
+
// Variation selectors (FE00-FE0F) and ZWJ (200D) take 0 width
|
|
832
|
+
if (code >= 0xFE00 && code <= 0xFE0F) {
|
|
833
|
+
i += 1;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (code === 0x200D) { // Zero width joiner
|
|
837
|
+
i += 1;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
// Emoji and wide characters take 2 cells
|
|
841
|
+
if (code > 0xFFFF) {
|
|
842
|
+
width += 2;
|
|
843
|
+
i += 2; // Skip surrogate pair
|
|
844
|
+
} else if (code > 127) {
|
|
845
|
+
width += 2;
|
|
846
|
+
i += 1;
|
|
847
|
+
} else {
|
|
848
|
+
width += 1;
|
|
849
|
+
i += 1;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return width;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// Pad string to display width
|
|
856
|
+
const padToWidth = (str, width) => {
|
|
857
|
+
const s = str || '';
|
|
858
|
+
const currentWidth = calcWidth(s);
|
|
859
|
+
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
695
860
|
};
|
|
696
861
|
|
|
697
862
|
if (success === 'X' || success === true) {
|
|
@@ -719,23 +884,15 @@ async function pull(gitUrl, branch = 'main', files = null, transportRequest = nu
|
|
|
719
884
|
|
|
720
885
|
// Calculate column widths based on terminal width
|
|
721
886
|
const tableWidth = Math.min(TERM_WIDTH, 120);
|
|
722
|
-
const iconCol =
|
|
887
|
+
const iconCol = 4; // Fixed width for icon column
|
|
723
888
|
const objCol = 28;
|
|
724
889
|
const msgCol = tableWidth - iconCol - objCol - 6; // Account for vertical lines (3 chars)
|
|
725
890
|
|
|
726
|
-
//
|
|
727
|
-
const getVisibleWidth = (str) => {
|
|
728
|
-
let width = 0;
|
|
729
|
-
for (const char of str) {
|
|
730
|
-
width += (char.charCodeAt(0) > 127) ? 2 : 1; // Emoji/wide chars = 2
|
|
731
|
-
}
|
|
732
|
-
return width;
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
// Helper to pad to visible width
|
|
891
|
+
// Pad string to display width using calcWidth for emoji support
|
|
736
892
|
const padToWidth = (str, width) => {
|
|
737
|
-
const
|
|
738
|
-
|
|
893
|
+
const s = str || '';
|
|
894
|
+
const currentWidth = calcWidth(s);
|
|
895
|
+
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
739
896
|
};
|
|
740
897
|
|
|
741
898
|
const headerLine = '─'.repeat(iconCol) + '┼' + '─'.repeat(objCol) + '┼' + '─'.repeat(msgCol);
|
|
@@ -1293,8 +1450,12 @@ Examples:
|
|
|
1293
1450
|
const config = loadConfig();
|
|
1294
1451
|
const csrfToken = await fetchCsrfToken(config);
|
|
1295
1452
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1453
|
+
// Send all files in one request
|
|
1454
|
+
const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config);
|
|
1455
|
+
|
|
1456
|
+
// Process results
|
|
1457
|
+
for (const result of results) {
|
|
1458
|
+
await processInspectResult(result);
|
|
1298
1459
|
}
|
|
1299
1460
|
break;
|
|
1300
1461
|
}
|
|
@@ -1430,9 +1591,9 @@ Examples:
|
|
|
1430
1591
|
console.log(` ${description}`);
|
|
1431
1592
|
}
|
|
1432
1593
|
|
|
1433
|
-
// Display source code for classes and
|
|
1594
|
+
// Display source code for classes, interfaces, and CDS views
|
|
1434
1595
|
const source = obj.SOURCE || obj.source || '';
|
|
1435
|
-
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class')) {
|
|
1596
|
+
if (source && (objType === 'INTF' || objType === 'Interface' || objType === 'CLAS' || objType === 'Class' || objType === 'DDLS' || objType === 'CDS View')) {
|
|
1436
1597
|
console.log('');
|
|
1437
1598
|
// Replace escaped newlines with actual newlines and display
|
|
1438
1599
|
const displaySource = source.replace(/\\n/g, '\n');
|
|
@@ -1500,6 +1661,15 @@ Examples:
|
|
|
1500
1661
|
}
|
|
1501
1662
|
|
|
1502
1663
|
console.log(end);
|
|
1664
|
+
} else if (objType === 'TTYP' || objType === 'Table Type') {
|
|
1665
|
+
// Show TTYP details as simple text lines
|
|
1666
|
+
console.log('');
|
|
1667
|
+
for (const comp of components) {
|
|
1668
|
+
const desc = comp.DESCRIPTION || comp.description || '';
|
|
1669
|
+
if (desc) {
|
|
1670
|
+
console.log(` ${desc}`);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1503
1673
|
} else {
|
|
1504
1674
|
// Build table display for TABL/STRU with Data Element and Description
|
|
1505
1675
|
const colWidths = {
|
|
@@ -1556,6 +1726,243 @@ Examples:
|
|
|
1556
1726
|
break;
|
|
1557
1727
|
}
|
|
1558
1728
|
|
|
1729
|
+
case 'preview': {
|
|
1730
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
1731
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
1732
|
+
console.error('Error: --objects parameter required');
|
|
1733
|
+
console.error('Usage: abapgit-agent preview --objects <table1>,<view1>,... [--type <type>] [--limit <n>] [--where <condition>] [--columns <cols>] [--vertical] [--compact] [--json]');
|
|
1734
|
+
console.error('Example: abapgit-agent preview --objects SFLIGHT');
|
|
1735
|
+
console.error('Example: abapgit-agent preview --objects ZC_MY_CDS_VIEW --type DDLS');
|
|
1736
|
+
console.error('Example: abapgit-agent preview --objects SFLIGHT --where "CARRID = \'AA\'"');
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
1741
|
+
const typeArg = args.indexOf('--type');
|
|
1742
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
1743
|
+
const limitArg = args.indexOf('--limit');
|
|
1744
|
+
const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 10;
|
|
1745
|
+
const whereArg = args.indexOf('--where');
|
|
1746
|
+
const where = whereArg !== -1 ? args[whereArg + 1] : null;
|
|
1747
|
+
const columnsArg = args.indexOf('--columns');
|
|
1748
|
+
const columns = columnsArg !== -1 ? args[columnsArg + 1].split(',').map(c => c.trim().toUpperCase()) : null;
|
|
1749
|
+
const verticalOutput = args.includes('--vertical');
|
|
1750
|
+
const compactOutput = args.includes('--compact');
|
|
1751
|
+
const jsonOutput = args.includes('--json');
|
|
1752
|
+
|
|
1753
|
+
console.log(`\n Previewing ${objects.length} object(s)`);
|
|
1754
|
+
|
|
1755
|
+
const config = loadConfig();
|
|
1756
|
+
const csrfToken = await fetchCsrfToken(config);
|
|
1757
|
+
|
|
1758
|
+
const data = {
|
|
1759
|
+
objects: objects,
|
|
1760
|
+
limit: Math.min(Math.max(1, limit), 100)
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
if (type) {
|
|
1764
|
+
data.type = type;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
if (where) {
|
|
1768
|
+
data.where = where;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
if (columns) {
|
|
1772
|
+
data.columns = columns;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
const result = await request('POST', '/sap/bc/z_abapgit_agent/preview', data, { csrfToken });
|
|
1776
|
+
|
|
1777
|
+
// Handle uppercase keys from ABAP
|
|
1778
|
+
const success = result.SUCCESS || result.success;
|
|
1779
|
+
const previewObjects = result.OBJECTS || result.objects || [];
|
|
1780
|
+
const message = result.MESSAGE || result.message || '';
|
|
1781
|
+
const error = result.ERROR || result.error;
|
|
1782
|
+
|
|
1783
|
+
if (!success || error) {
|
|
1784
|
+
console.error(`\n Error: ${error || 'Failed to preview objects'}`);
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (jsonOutput) {
|
|
1789
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1790
|
+
} else {
|
|
1791
|
+
console.log(`\n ${message}`);
|
|
1792
|
+
console.log('');
|
|
1793
|
+
|
|
1794
|
+
// Track if columns were explicitly specified
|
|
1795
|
+
const columnsExplicit = columns !== null;
|
|
1796
|
+
|
|
1797
|
+
for (let i = 0; i < previewObjects.length; i++) {
|
|
1798
|
+
const obj = previewObjects[i];
|
|
1799
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
1800
|
+
const objType = obj.TYPE || obj.type || '';
|
|
1801
|
+
const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
|
|
1802
|
+
// Parse rows - could be a JSON string or array
|
|
1803
|
+
let rows = obj.ROWS || obj.rows || [];
|
|
1804
|
+
if (typeof rows === 'string') {
|
|
1805
|
+
try {
|
|
1806
|
+
rows = JSON.parse(rows);
|
|
1807
|
+
} catch (e) {
|
|
1808
|
+
rows = [];
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
const fields = obj.FIELDS || obj.fields || [];
|
|
1812
|
+
const rowCount = obj.ROW_COUNT || obj.row_count || 0;
|
|
1813
|
+
const totalRows = obj.TOTAL_ROWS || obj.total_rows || 0;
|
|
1814
|
+
const notFound = obj.NOT_FOUND || obj.not_found || false;
|
|
1815
|
+
const accessDenied = obj.ACCESS_DENIED || obj.access_denied || false;
|
|
1816
|
+
|
|
1817
|
+
// Check if object was not found
|
|
1818
|
+
if (notFound) {
|
|
1819
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
1820
|
+
console.log(` Object not found: ${objName}`);
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// Check if access denied
|
|
1825
|
+
if (accessDenied) {
|
|
1826
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
1827
|
+
console.log(` Access denied to: ${objName}`);
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
console.log(` 📊 Preview: ${objName} (${objTypeText})`);
|
|
1832
|
+
|
|
1833
|
+
// Check for errors first
|
|
1834
|
+
const objError = obj.ERROR || obj.error;
|
|
1835
|
+
if (objError) {
|
|
1836
|
+
console.log(` ❌ Error: ${objError}`);
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (rows.length === 0) {
|
|
1841
|
+
console.log(' No data found');
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// Get all unique field names from all rows
|
|
1846
|
+
const allFields = new Set();
|
|
1847
|
+
rows.forEach(row => {
|
|
1848
|
+
Object.keys(row).forEach(key => allFields.add(key));
|
|
1849
|
+
});
|
|
1850
|
+
const allFieldNames = Array.from(allFields);
|
|
1851
|
+
|
|
1852
|
+
// Display as table - use fields metadata only if --columns was explicitly specified
|
|
1853
|
+
let fieldNames;
|
|
1854
|
+
let columnsAutoSelected = false;
|
|
1855
|
+
if (columnsExplicit && fields && fields.length > 0) {
|
|
1856
|
+
// Use fields from metadata (filtered by explicit --columns)
|
|
1857
|
+
fieldNames = fields.map(f => f.FIELD || f.field);
|
|
1858
|
+
} else {
|
|
1859
|
+
// Use all fields - let terminal handle wrapping if needed
|
|
1860
|
+
// Terminal width detection is unreliable without a proper TTY
|
|
1861
|
+
fieldNames = allFieldNames;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// Calculate column widths - use reasonable defaults
|
|
1865
|
+
const colWidths = {};
|
|
1866
|
+
const maxColWidth = compactOutput ? 10 : 20;
|
|
1867
|
+
fieldNames.forEach(field => {
|
|
1868
|
+
let maxWidth = field.length;
|
|
1869
|
+
rows.forEach(row => {
|
|
1870
|
+
const value = String(row[field] || '');
|
|
1871
|
+
maxWidth = Math.max(maxWidth, value.length);
|
|
1872
|
+
});
|
|
1873
|
+
// Cap at maxColWidth (truncates both headers and data in compact mode)
|
|
1874
|
+
colWidths[field] = Math.min(maxWidth, maxColWidth);
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
// Render output - either vertical or table
|
|
1878
|
+
if (verticalOutput) {
|
|
1879
|
+
// Vertical format: each field on its own line
|
|
1880
|
+
rows.forEach((row, rowIndex) => {
|
|
1881
|
+
if (rows.length > 1) {
|
|
1882
|
+
console.log(`\n Row ${rowIndex + 1}:`);
|
|
1883
|
+
console.log(' ' + '─'.repeat(30));
|
|
1884
|
+
}
|
|
1885
|
+
fieldNames.forEach(field => {
|
|
1886
|
+
const value = String(row[field] || '');
|
|
1887
|
+
console.log(` ${field}: ${value}`);
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
console.log('');
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// Build table header
|
|
1895
|
+
let headerLine = ' ┌';
|
|
1896
|
+
let separatorLine = ' ├';
|
|
1897
|
+
fieldNames.forEach(field => {
|
|
1898
|
+
const width = colWidths[field];
|
|
1899
|
+
headerLine += '─'.repeat(width + 2) + '┬';
|
|
1900
|
+
separatorLine += '─'.repeat(width + 2) + '┼';
|
|
1901
|
+
});
|
|
1902
|
+
headerLine = headerLine.slice(0, -1) + '┐';
|
|
1903
|
+
separatorLine = separatorLine.slice(0, -1) + '┤';
|
|
1904
|
+
|
|
1905
|
+
// Build header row
|
|
1906
|
+
let headerRow = ' │';
|
|
1907
|
+
fieldNames.forEach(field => {
|
|
1908
|
+
const width = colWidths[field];
|
|
1909
|
+
let displayField = String(field);
|
|
1910
|
+
if (displayField.length > width) {
|
|
1911
|
+
displayField = displayField.slice(0, width - 3) + '...';
|
|
1912
|
+
}
|
|
1913
|
+
headerRow += ' ' + displayField.padEnd(width) + ' │';
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
console.log(headerLine);
|
|
1917
|
+
console.log(headerRow);
|
|
1918
|
+
console.log(separatorLine);
|
|
1919
|
+
|
|
1920
|
+
// Build data rows
|
|
1921
|
+
rows.forEach(row => {
|
|
1922
|
+
let dataRow = ' │';
|
|
1923
|
+
fieldNames.forEach(field => {
|
|
1924
|
+
const width = colWidths[field];
|
|
1925
|
+
const value = String(row[field] || '');
|
|
1926
|
+
const displayValue = value.length > width ? value.slice(0, width - 3) + '...' : value;
|
|
1927
|
+
dataRow += ' ' + displayValue.padEnd(width) + ' │';
|
|
1928
|
+
});
|
|
1929
|
+
console.log(dataRow);
|
|
1930
|
+
});
|
|
1931
|
+
|
|
1932
|
+
// Build bottom border
|
|
1933
|
+
let bottomLine = ' └';
|
|
1934
|
+
fieldNames.forEach(field => {
|
|
1935
|
+
const width = colWidths[field];
|
|
1936
|
+
bottomLine += '─'.repeat(width + 2) + '┴';
|
|
1937
|
+
});
|
|
1938
|
+
bottomLine = bottomLine.slice(0, -1) + '┘';
|
|
1939
|
+
console.log(bottomLine);
|
|
1940
|
+
|
|
1941
|
+
// Show row count
|
|
1942
|
+
if (totalRows > rowCount) {
|
|
1943
|
+
console.log(`\n Showing ${rowCount} of ${totalRows} rows`);
|
|
1944
|
+
} else {
|
|
1945
|
+
console.log(`\n ${rowCount} row(s)`);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// Note about hidden columns only when --columns was explicitly specified
|
|
1949
|
+
const columnsDisplayed = fieldNames.length;
|
|
1950
|
+
let columnsHidden = [];
|
|
1951
|
+
|
|
1952
|
+
if (columnsExplicit) {
|
|
1953
|
+
columnsHidden = obj.COLUMNS_HIDDEN || obj.columns_hidden || [];
|
|
1954
|
+
if (columnsHidden.length > 0) {
|
|
1955
|
+
console.log(`\n ⚠️ ${columnsHidden.length} more columns hidden (${columnsHidden.join(', ')})`);
|
|
1956
|
+
console.log(' Use --json for full data');
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
console.log('');
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1559
1966
|
case 'help':
|
|
1560
1967
|
case '--help':
|
|
1561
1968
|
case '-h':
|