abapgit-agent 1.7.1 → 1.8.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/.abapGitAgent.example +11 -0
- package/README.md +7 -7
- package/abap/.github/copilot-instructions.md +254 -0
- package/abap/CLAUDE.md +432 -0
- package/abap/guidelines/00_index.md +8 -0
- package/abap/guidelines/01_sql.md +8 -0
- package/abap/guidelines/02_exceptions.md +8 -0
- package/abap/guidelines/03_testing.md +8 -0
- package/abap/guidelines/04_cds.md +8 -0
- package/abap/guidelines/05_classes.md +8 -0
- package/abap/guidelines/06_objects.md +8 -0
- package/abap/guidelines/07_json.md +8 -0
- package/abap/guidelines/08_abapgit.md +8 -0
- package/abap/guidelines/09_unit_testable_code.md +8 -0
- package/bin/abapgit-agent +61 -2789
- package/package.json +25 -5
- package/src/agent.js +213 -20
- package/src/commands/create.js +102 -0
- package/src/commands/delete.js +72 -0
- package/src/commands/health.js +24 -0
- package/src/commands/help.js +111 -0
- package/src/commands/import.js +99 -0
- package/src/commands/init.js +321 -0
- package/src/commands/inspect.js +184 -0
- package/src/commands/list.js +143 -0
- package/src/commands/preview.js +277 -0
- package/src/commands/pull.js +278 -0
- package/src/commands/ref.js +96 -0
- package/src/commands/status.js +52 -0
- package/src/commands/syntax.js +290 -0
- package/src/commands/tree.js +209 -0
- package/src/commands/unit.js +133 -0
- package/src/commands/view.js +215 -0
- package/src/commands/where.js +138 -0
- package/src/config.js +11 -1
- package/src/utils/abap-http.js +347 -0
- package/src/{ref-search.js → utils/abap-reference.js} +119 -1
- package/src/utils/git-utils.js +58 -0
- package/src/utils/validators.js +72 -0
- package/src/utils/version-check.js +80 -0
- package/src/abap-client.js +0 -523
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspect command - Syntax check for ABAP files
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const pathModule = require('path');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inspect all files in one request
|
|
9
|
+
*/
|
|
10
|
+
async function inspectAllFiles(files, csrfToken, config, variant, http) {
|
|
11
|
+
// Convert files to uppercase names
|
|
12
|
+
const fileNames = files.map(f => {
|
|
13
|
+
const baseName = pathModule.basename(f).toUpperCase();
|
|
14
|
+
return baseName;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Send all files in one request
|
|
19
|
+
const data = {
|
|
20
|
+
files: fileNames
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Add variant if specified
|
|
24
|
+
if (variant) {
|
|
25
|
+
data.variant = variant;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/inspect', data, { csrfToken });
|
|
29
|
+
|
|
30
|
+
// Handle both table result and old single result
|
|
31
|
+
let results = [];
|
|
32
|
+
if (Array.isArray(result)) {
|
|
33
|
+
results = result;
|
|
34
|
+
} else {
|
|
35
|
+
// Convert single result to array format
|
|
36
|
+
results = [{
|
|
37
|
+
OBJECT_TYPE: 'UNKNOWN',
|
|
38
|
+
OBJECT_NAME: files.join(', '),
|
|
39
|
+
SUCCESS: result.SUCCESS !== undefined ? result.SUCCESS === 'X' || result.SUCCESS === true : result.success,
|
|
40
|
+
ERROR_COUNT: result.ERROR_COUNT || result.error_count || 0,
|
|
41
|
+
ERRORS: result.ERRORS || result.errors || [],
|
|
42
|
+
WARNINGS: result.warnings || []
|
|
43
|
+
}];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`\n Error: ${error.message}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Process a single inspect result
|
|
55
|
+
*/
|
|
56
|
+
async function processInspectResult(res) {
|
|
57
|
+
// Handle both uppercase and lowercase keys
|
|
58
|
+
const success = res.SUCCESS !== undefined ? res.SUCCESS : res.success;
|
|
59
|
+
const objectType = res.OBJECT_TYPE !== undefined ? res.OBJECT_TYPE : res.object_type;
|
|
60
|
+
const objectName = res.OBJECT_NAME !== undefined ? res.OBJECT_NAME : res.object_name;
|
|
61
|
+
const errorCount = res.ERROR_COUNT !== undefined ? res.ERROR_COUNT : (res.error_count || 0);
|
|
62
|
+
const errors = res.ERRORS !== undefined ? res.ERRORS : (res.errors || []);
|
|
63
|
+
const warnings = res.WARNINGS !== undefined ? res.WARNINGS : (res.warnings || []);
|
|
64
|
+
const infos = res.INFOS !== undefined ? res.INFOS : (res.infos || []);
|
|
65
|
+
|
|
66
|
+
if (errorCount > 0 || warnings.length > 0 || infos.length > 0) {
|
|
67
|
+
if (errorCount > 0) {
|
|
68
|
+
console.log(`❌ ${objectType} ${objectName} - Syntax check failed (${errorCount} error(s)):`);
|
|
69
|
+
} else {
|
|
70
|
+
const total = warnings.length + infos.length;
|
|
71
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check passed with warnings (${total}):`);
|
|
72
|
+
}
|
|
73
|
+
console.log('\nErrors:');
|
|
74
|
+
console.log('─'.repeat(60));
|
|
75
|
+
|
|
76
|
+
for (const err of errors) {
|
|
77
|
+
const line = err.LINE || err.line || '?';
|
|
78
|
+
const column = err.COLUMN || err.column || '?';
|
|
79
|
+
const text = err.TEXT || err.text || 'Unknown error';
|
|
80
|
+
const methodName = err.METHOD_NAME || err.method_name;
|
|
81
|
+
const sobjname = err.SOBJNAME || err.sobjname;
|
|
82
|
+
|
|
83
|
+
if (methodName) {
|
|
84
|
+
console.log(` Method: ${methodName}`);
|
|
85
|
+
}
|
|
86
|
+
console.log(` Line ${line}, Column ${column}:`);
|
|
87
|
+
if (sobjname && sobjname.includes('====')) {
|
|
88
|
+
console.log(` Include: ${sobjname}`);
|
|
89
|
+
}
|
|
90
|
+
console.log(` ${text}`);
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Show warnings if any
|
|
95
|
+
if (warnings.length > 0) {
|
|
96
|
+
console.log('Warnings:');
|
|
97
|
+
console.log('─'.repeat(60));
|
|
98
|
+
for (const warn of warnings) {
|
|
99
|
+
const line = warn.LINE || warn.line || '?';
|
|
100
|
+
const text = warn.MESSAGE || warn.message || 'Unknown warning';
|
|
101
|
+
const methodName = warn.METHOD_NAME || warn.method_name;
|
|
102
|
+
const sobjname = warn.SOBJNAME || warn.sobjname;
|
|
103
|
+
|
|
104
|
+
if (methodName) {
|
|
105
|
+
console.log(` Method: ${methodName}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(` Line ${line}:`);
|
|
108
|
+
if (sobjname && sobjname.includes('====')) {
|
|
109
|
+
console.log(` Include: ${sobjname}`);
|
|
110
|
+
}
|
|
111
|
+
console.log(` ${text}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Show infos if any
|
|
116
|
+
if (infos.length > 0) {
|
|
117
|
+
console.log('Info:');
|
|
118
|
+
console.log('─'.repeat(60));
|
|
119
|
+
for (const info of infos) {
|
|
120
|
+
const line = info.LINE || info.line || '?';
|
|
121
|
+
const text = info.MESSAGE || info.message || 'Unknown info';
|
|
122
|
+
const methodName = info.METHOD_NAME || info.method_name;
|
|
123
|
+
const sobjname = info.SOBJNAME || info.sobjname;
|
|
124
|
+
|
|
125
|
+
if (methodName) {
|
|
126
|
+
console.log(` Method: ${methodName}`);
|
|
127
|
+
}
|
|
128
|
+
console.log(` Line ${line}:`);
|
|
129
|
+
if (sobjname && sobjname.includes('====')) {
|
|
130
|
+
console.log(` Include: ${sobjname}`);
|
|
131
|
+
}
|
|
132
|
+
console.log(` ${text}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else if (success === true || success === 'X') {
|
|
136
|
+
console.log(`✅ ${objectType} ${objectName} - Syntax check passed`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`⚠️ ${objectType} ${objectName} - Syntax check returned unexpected status`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
name: 'inspect',
|
|
144
|
+
description: 'Inspect ABAP source files for syntax issues',
|
|
145
|
+
requiresAbapConfig: true,
|
|
146
|
+
requiresVersionCheck: true,
|
|
147
|
+
|
|
148
|
+
async execute(args, context) {
|
|
149
|
+
const { loadConfig, AbapHttp } = context;
|
|
150
|
+
|
|
151
|
+
const filesArgIndex = args.indexOf('--files');
|
|
152
|
+
if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
|
|
153
|
+
console.error('Error: --files parameter required');
|
|
154
|
+
console.error('Usage: abapgit-agent inspect --files <file1>,<file2>,... [--variant <check-variant>]');
|
|
155
|
+
console.error('Example: abapgit-agent inspect --files src/zcl_my_class.clas.abap');
|
|
156
|
+
console.error('Example: abapgit-agent inspect --files src/zcl_my_class.clas.abap --variant ALL_CHECKS');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const filesSyntaxCheck = args[filesArgIndex + 1].split(',').map(f => f.trim());
|
|
161
|
+
|
|
162
|
+
// Parse optional --variant parameter
|
|
163
|
+
const variantArgIndex = args.indexOf('--variant');
|
|
164
|
+
const variant = variantArgIndex !== -1 ? args[variantArgIndex + 1] : null;
|
|
165
|
+
|
|
166
|
+
console.log(`\n Inspect for ${filesSyntaxCheck.length} file(s)`);
|
|
167
|
+
if (variant) {
|
|
168
|
+
console.log(` Using variant: ${variant}`);
|
|
169
|
+
}
|
|
170
|
+
console.log('');
|
|
171
|
+
|
|
172
|
+
const config = loadConfig();
|
|
173
|
+
const http = new AbapHttp(config);
|
|
174
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
175
|
+
|
|
176
|
+
// Send all files in one request
|
|
177
|
+
const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config, variant, http);
|
|
178
|
+
|
|
179
|
+
// Process results
|
|
180
|
+
for (const result of results) {
|
|
181
|
+
await processInspectResult(result);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List command - List ABAP objects in a package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'list',
|
|
7
|
+
description: 'List ABAP objects in a package',
|
|
8
|
+
requiresAbapConfig: true,
|
|
9
|
+
requiresVersionCheck: true,
|
|
10
|
+
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const { loadConfig, AbapHttp } = context;
|
|
13
|
+
|
|
14
|
+
const packageArgIndex = args.indexOf('--package');
|
|
15
|
+
if (packageArgIndex === -1) {
|
|
16
|
+
console.error('Error: --package parameter required');
|
|
17
|
+
console.error('Usage: abapgit-agent list --package <package> [--type <types>] [--name <pattern>] [--limit <n>] [--offset <n>] [--json]');
|
|
18
|
+
console.error('Example: abapgit-agent list --package \'$ZMY_PACKAGE\'');
|
|
19
|
+
console.error('Example: abapgit-agent list --package \'$ZMY_PACKAGE\' --type CLAS,INTF');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if package value is missing
|
|
24
|
+
if (packageArgIndex + 1 >= args.length) {
|
|
25
|
+
console.error('Error: --package parameter value is missing');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const packageName = args[packageArgIndex + 1];
|
|
30
|
+
|
|
31
|
+
// Validate package name
|
|
32
|
+
if (!packageName || packageName.trim() === '') {
|
|
33
|
+
console.error('Error: --package parameter cannot be empty');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Optional type parameter
|
|
38
|
+
const typeArgIndex = args.indexOf('--type');
|
|
39
|
+
const type = typeArgIndex !== -1 && typeArgIndex + 1 < args.length ? args[typeArgIndex + 1] : null;
|
|
40
|
+
|
|
41
|
+
// Optional name pattern
|
|
42
|
+
const nameArgIndex = args.indexOf('--name');
|
|
43
|
+
const name = nameArgIndex !== -1 && nameArgIndex + 1 < args.length ? args[nameArgIndex + 1] : null;
|
|
44
|
+
|
|
45
|
+
// Optional limit
|
|
46
|
+
const limitArgIndex = args.indexOf('--limit');
|
|
47
|
+
let limit = 100;
|
|
48
|
+
if (limitArgIndex !== -1 && limitArgIndex + 1 < args.length) {
|
|
49
|
+
limit = parseInt(args[limitArgIndex + 1], 10);
|
|
50
|
+
if (isNaN(limit) || limit < 1) {
|
|
51
|
+
console.error('Error: --limit must be a positive number');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
if (limit > 1000) {
|
|
55
|
+
console.error('Error: --limit value too high (max: 1000)');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Optional offset
|
|
61
|
+
const offsetArgIndex = args.indexOf('--offset');
|
|
62
|
+
let offset = 0;
|
|
63
|
+
if (offsetArgIndex !== -1 && offsetArgIndex + 1 < args.length) {
|
|
64
|
+
offset = parseInt(args[offsetArgIndex + 1], 10);
|
|
65
|
+
if (isNaN(offset) || offset < 0) {
|
|
66
|
+
console.error('Error: --offset must be a non-negative number');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Optional json parameter
|
|
72
|
+
const jsonOutput = args.includes('--json');
|
|
73
|
+
|
|
74
|
+
const config = loadConfig();
|
|
75
|
+
const http = new AbapHttp(config);
|
|
76
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
77
|
+
|
|
78
|
+
const data = {
|
|
79
|
+
package: packageName,
|
|
80
|
+
limit: limit,
|
|
81
|
+
offset: offset
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (type) {
|
|
85
|
+
data.type = type;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (name) {
|
|
89
|
+
data.name = name;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/list', data, { csrfToken });
|
|
93
|
+
|
|
94
|
+
// Handle uppercase keys from ABAP
|
|
95
|
+
const success = result.SUCCESS || result.success;
|
|
96
|
+
const error = result.ERROR || result.error;
|
|
97
|
+
const objects = result.OBJECTS || result.objects || [];
|
|
98
|
+
const byType = result.BY_TYPE || result.by_type || [];
|
|
99
|
+
const total = result.TOTAL || result.total || 0;
|
|
100
|
+
|
|
101
|
+
if (!success || error) {
|
|
102
|
+
console.error(`\n Error: ${error || 'Failed to list objects'}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (jsonOutput) {
|
|
107
|
+
console.log(JSON.stringify(result, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
// Display human-readable output
|
|
110
|
+
let title = `Objects in ${packageName}`;
|
|
111
|
+
if (type) {
|
|
112
|
+
title += ` (${type} only`;
|
|
113
|
+
if (total !== objects.length) {
|
|
114
|
+
title += `, Total: ${total}`;
|
|
115
|
+
}
|
|
116
|
+
title += ')';
|
|
117
|
+
} else if (total !== objects.length) {
|
|
118
|
+
title += ` (Total: ${total})`;
|
|
119
|
+
}
|
|
120
|
+
console.log(`\n${title}\n`);
|
|
121
|
+
|
|
122
|
+
// Group objects by type
|
|
123
|
+
const objectsByType = {};
|
|
124
|
+
for (const obj of objects) {
|
|
125
|
+
const objType = (obj.TYPE || obj.type || '???').toUpperCase();
|
|
126
|
+
if (!objectsByType[objType]) {
|
|
127
|
+
objectsByType[objType] = [];
|
|
128
|
+
}
|
|
129
|
+
objectsByType[objType].push(obj.NAME || obj.name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Display grouped objects
|
|
133
|
+
for (const objType of Object.keys(objectsByType).sort()) {
|
|
134
|
+
const objNames = objectsByType[objType];
|
|
135
|
+
console.log(` ${objType} (${objNames.length})`);
|
|
136
|
+
for (const objName of objNames) {
|
|
137
|
+
console.log(` ${objName}`);
|
|
138
|
+
}
|
|
139
|
+
console.log('');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview command - Preview data from ABAP tables or CDS views
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'preview',
|
|
7
|
+
description: 'Preview data from ABAP tables or CDS views',
|
|
8
|
+
requiresAbapConfig: true,
|
|
9
|
+
requiresVersionCheck: true,
|
|
10
|
+
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const { loadConfig, AbapHttp, validators } = context;
|
|
13
|
+
|
|
14
|
+
const objectsArgIndex = args.indexOf('--objects');
|
|
15
|
+
if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
|
|
16
|
+
console.error('Error: --objects parameter required');
|
|
17
|
+
console.error('Usage: abapgit-agent preview --objects <table1>,<view1>,... [--type <type>] [--limit <n>] [--offset <n>] [--where <condition>] [--columns <cols>] [--vertical] [--compact] [--json]');
|
|
18
|
+
console.error('Example: abapgit-agent preview --objects SFLIGHT');
|
|
19
|
+
console.error('Example: abapgit-agent preview --objects ZC_MY_CDS_VIEW --type DDLS');
|
|
20
|
+
console.error('Example: abapgit-agent preview --objects SFLIGHT --where "CARRID = \'AA\'"');
|
|
21
|
+
console.error('Example: abapgit-agent preview --objects SFLIGHT --offset 10 --limit 20');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
|
|
26
|
+
const typeArg = args.indexOf('--type');
|
|
27
|
+
const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
|
|
28
|
+
const limitArg = args.indexOf('--limit');
|
|
29
|
+
const limitRaw = limitArg !== -1 ? args[limitArg + 1] : null;
|
|
30
|
+
const limitParsed = parseInt(limitRaw, 10);
|
|
31
|
+
const limit = limitRaw && !limitRaw.startsWith('--') && !isNaN(limitParsed) ? Math.max(1, limitParsed) : 100;
|
|
32
|
+
const offsetArg = args.indexOf('--offset');
|
|
33
|
+
const offsetRaw = offsetArg !== -1 ? args[offsetArg + 1] : null;
|
|
34
|
+
const offsetParsed = parseInt(offsetRaw, 10);
|
|
35
|
+
const offset = offsetRaw && !offsetRaw.startsWith('--') && !isNaN(offsetParsed) ? Math.max(0, offsetParsed) : 0;
|
|
36
|
+
const whereArg = args.indexOf('--where');
|
|
37
|
+
const where = whereArg !== -1 ? args[whereArg + 1] : null;
|
|
38
|
+
const columnsArg = args.indexOf('--columns');
|
|
39
|
+
const columns = columnsArg !== -1 ? args[columnsArg + 1].split(',').map(c => c.trim().toUpperCase()) : null;
|
|
40
|
+
const verticalOutput = args.includes('--vertical');
|
|
41
|
+
const compactOutput = args.includes('--compact');
|
|
42
|
+
const jsonOutput = args.includes('--json');
|
|
43
|
+
|
|
44
|
+
console.log(`\n Previewing ${objects.length} object(s)`);
|
|
45
|
+
|
|
46
|
+
const config = loadConfig();
|
|
47
|
+
const http = new AbapHttp(config);
|
|
48
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
49
|
+
|
|
50
|
+
const data = {
|
|
51
|
+
objects: objects,
|
|
52
|
+
limit: Math.min(Math.max(1, limit), 500),
|
|
53
|
+
offset: Math.max(0, offset)
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (type) {
|
|
57
|
+
data.type = type;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (where) {
|
|
61
|
+
data.where = validators.convertDatesInWhereClause(where);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (columns) {
|
|
65
|
+
data.columns = columns;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/preview', data, { csrfToken });
|
|
69
|
+
|
|
70
|
+
// Handle uppercase keys from ABAP
|
|
71
|
+
const success = result.SUCCESS || result.success;
|
|
72
|
+
const previewObjects = result.OBJECTS || result.objects || [];
|
|
73
|
+
const message = result.MESSAGE || result.message || '';
|
|
74
|
+
const error = result.ERROR || result.error;
|
|
75
|
+
|
|
76
|
+
if (!success || error) {
|
|
77
|
+
console.error(`\n Error: ${error || 'Failed to preview objects'}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pagination = result.PAGINATION || result.pagination || null;
|
|
82
|
+
|
|
83
|
+
if (jsonOutput) {
|
|
84
|
+
console.log(JSON.stringify(result, null, 2));
|
|
85
|
+
} else {
|
|
86
|
+
// Build pagination message
|
|
87
|
+
let paginationMsg = '';
|
|
88
|
+
const paginationTotal = pagination ? (pagination.TOTAL || pagination.total || 0) : 0;
|
|
89
|
+
const paginationHasMore = pagination ? (pagination.HAS_MORE || pagination.has_more || false) : false;
|
|
90
|
+
if (pagination && paginationTotal > 0) {
|
|
91
|
+
// Handle case where offset exceeds total
|
|
92
|
+
if (offset >= paginationTotal) {
|
|
93
|
+
paginationMsg = ` (Offset ${offset} exceeds total ${paginationTotal})`;
|
|
94
|
+
} else {
|
|
95
|
+
const start = offset + 1;
|
|
96
|
+
const end = Math.min(offset + limit, paginationTotal);
|
|
97
|
+
paginationMsg = ` (Showing ${start}-${end} of ${paginationTotal})`;
|
|
98
|
+
if (paginationHasMore) {
|
|
99
|
+
paginationMsg += ` — Use --offset ${offset + limit} to see more`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`\n ${message}${paginationMsg}`);
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
// Track if columns were explicitly specified
|
|
108
|
+
const columnsExplicit = columns !== null;
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < previewObjects.length; i++) {
|
|
111
|
+
const obj = previewObjects[i];
|
|
112
|
+
const objName = obj.NAME || obj.name || `Object ${i + 1}`;
|
|
113
|
+
const objType = obj.TYPE || obj.type || '';
|
|
114
|
+
const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
|
|
115
|
+
// Parse rows - could be a JSON string or array
|
|
116
|
+
let rows = obj.ROWS || obj.rows || [];
|
|
117
|
+
if (typeof rows === 'string') {
|
|
118
|
+
try {
|
|
119
|
+
rows = JSON.parse(rows);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
rows = [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const fields = obj.FIELDS || obj.fields || [];
|
|
125
|
+
const rowCount = obj.ROW_COUNT || obj.row_count || 0;
|
|
126
|
+
const totalRows = obj.TOTAL_ROWS || obj.total_rows || 0;
|
|
127
|
+
const notFound = obj.NOT_FOUND || obj.not_found || false;
|
|
128
|
+
const accessDenied = obj.ACCESS_DENIED || obj.access_denied || false;
|
|
129
|
+
|
|
130
|
+
// Check if object was not found
|
|
131
|
+
if (notFound) {
|
|
132
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
133
|
+
console.log(` Object not found: ${objName}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if access denied
|
|
138
|
+
if (accessDenied) {
|
|
139
|
+
console.log(` ❌ ${objName} (${objTypeText})`);
|
|
140
|
+
console.log(` Access denied to: ${objName}`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(` 📊 Preview: ${objName} (${objTypeText})`);
|
|
145
|
+
|
|
146
|
+
// Check for errors first
|
|
147
|
+
const objError = obj.ERROR || obj.error;
|
|
148
|
+
if (objError) {
|
|
149
|
+
console.log(` ❌ Error: ${objError}`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (rows.length === 0) {
|
|
154
|
+
console.log(' No data found');
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get all unique field names from all rows
|
|
159
|
+
const allFields = new Set();
|
|
160
|
+
rows.forEach(row => {
|
|
161
|
+
Object.keys(row).forEach(key => allFields.add(key));
|
|
162
|
+
});
|
|
163
|
+
const allFieldNames = Array.from(allFields);
|
|
164
|
+
|
|
165
|
+
// Display as table - use fields metadata only if --columns was explicitly specified
|
|
166
|
+
let fieldNames;
|
|
167
|
+
let columnsAutoSelected = false;
|
|
168
|
+
if (columnsExplicit && fields && fields.length > 0) {
|
|
169
|
+
// Use fields from metadata (filtered by explicit --columns)
|
|
170
|
+
fieldNames = fields.map(f => f.FIELD || f.field);
|
|
171
|
+
} else {
|
|
172
|
+
// Use all fields - let terminal handle wrapping if needed
|
|
173
|
+
// Terminal width detection is unreliable without a proper TTY
|
|
174
|
+
fieldNames = allFieldNames;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Calculate column widths - use reasonable defaults
|
|
178
|
+
const colWidths = {};
|
|
179
|
+
const maxColWidth = compactOutput ? 10 : 20;
|
|
180
|
+
fieldNames.forEach(field => {
|
|
181
|
+
let maxWidth = field.length;
|
|
182
|
+
rows.forEach(row => {
|
|
183
|
+
const value = String(row[field] || '');
|
|
184
|
+
maxWidth = Math.max(maxWidth, value.length);
|
|
185
|
+
});
|
|
186
|
+
// Cap at maxColWidth (truncates both headers and data in compact mode)
|
|
187
|
+
colWidths[field] = Math.min(maxWidth, maxColWidth);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Render output - either vertical or table
|
|
191
|
+
if (verticalOutput) {
|
|
192
|
+
// Vertical format: each field on its own line
|
|
193
|
+
rows.forEach((row, rowIndex) => {
|
|
194
|
+
if (rows.length > 1) {
|
|
195
|
+
console.log(`\n Row ${rowIndex + 1}:`);
|
|
196
|
+
console.log(' ' + '─'.repeat(30));
|
|
197
|
+
}
|
|
198
|
+
fieldNames.forEach(field => {
|
|
199
|
+
const value = String(row[field] || '');
|
|
200
|
+
console.log(` ${field}: ${value}`);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
console.log('');
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Build table header
|
|
208
|
+
let headerLine = ' ┌';
|
|
209
|
+
let separatorLine = ' ├';
|
|
210
|
+
fieldNames.forEach(field => {
|
|
211
|
+
const width = colWidths[field];
|
|
212
|
+
headerLine += '─'.repeat(width + 2) + '┬';
|
|
213
|
+
separatorLine += '─'.repeat(width + 2) + '┼';
|
|
214
|
+
});
|
|
215
|
+
headerLine = headerLine.slice(0, -1) + '┐';
|
|
216
|
+
separatorLine = separatorLine.slice(0, -1) + '┤';
|
|
217
|
+
|
|
218
|
+
// Build header row
|
|
219
|
+
let headerRow = ' │';
|
|
220
|
+
fieldNames.forEach(field => {
|
|
221
|
+
const width = colWidths[field];
|
|
222
|
+
let displayField = String(field);
|
|
223
|
+
if (displayField.length > width) {
|
|
224
|
+
displayField = displayField.slice(0, width - 3) + '...';
|
|
225
|
+
}
|
|
226
|
+
headerRow += ' ' + displayField.padEnd(width) + ' │';
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
console.log(headerLine);
|
|
230
|
+
console.log(headerRow);
|
|
231
|
+
console.log(separatorLine);
|
|
232
|
+
|
|
233
|
+
// Build data rows
|
|
234
|
+
rows.forEach(row => {
|
|
235
|
+
let dataRow = ' │';
|
|
236
|
+
fieldNames.forEach(field => {
|
|
237
|
+
const width = colWidths[field];
|
|
238
|
+
const value = String(row[field] || '');
|
|
239
|
+
const displayValue = value.length > width ? value.slice(0, width - 3) + '...' : value;
|
|
240
|
+
dataRow += ' ' + displayValue.padEnd(width) + ' │';
|
|
241
|
+
});
|
|
242
|
+
console.log(dataRow);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Build bottom border
|
|
246
|
+
let bottomLine = ' └';
|
|
247
|
+
fieldNames.forEach(field => {
|
|
248
|
+
const width = colWidths[field];
|
|
249
|
+
bottomLine += '─'.repeat(width + 2) + '┴';
|
|
250
|
+
});
|
|
251
|
+
bottomLine = bottomLine.slice(0, -1) + '┘';
|
|
252
|
+
console.log(bottomLine);
|
|
253
|
+
|
|
254
|
+
// Show row count
|
|
255
|
+
if (totalRows > rowCount) {
|
|
256
|
+
console.log(`\n Showing ${rowCount} of ${totalRows} rows`);
|
|
257
|
+
} else {
|
|
258
|
+
console.log(`\n ${rowCount} row(s)`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Note about hidden columns only when --columns was explicitly specified
|
|
262
|
+
const columnsDisplayed = fieldNames.length;
|
|
263
|
+
let columnsHidden = [];
|
|
264
|
+
|
|
265
|
+
if (columnsExplicit) {
|
|
266
|
+
columnsHidden = obj.COLUMNS_HIDDEN || obj.columns_hidden || [];
|
|
267
|
+
if (columnsHidden.length > 0) {
|
|
268
|
+
console.log(`\n ⚠️ ${columnsHidden.length} more columns hidden (${columnsHidden.join(', ')})`);
|
|
269
|
+
console.log(' Use --json for full data');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log('');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|