abapgit-agent 1.13.1 ā 1.13.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/README.md +0 -1
- package/bin/abapgit-agent +1 -26
- package/package.json +1 -7
- package/src/commands/index.js +33 -0
- package/src/commands/pull.js +27 -38
- package/src/commands/view.js +2 -0
- package/src/agent.js +0 -512
- package/src/index.js +0 -48
- package/src/server.js +0 -116
package/README.md
CHANGED
|
@@ -115,7 +115,6 @@ npm run pull -- --url <git-url> --branch main
|
|
|
115
115
|
| Installation & Setup | [INSTALL.md](INSTALL.md) |
|
|
116
116
|
| All Commands Overview | [docs/commands.md](docs/commands.md) |
|
|
117
117
|
| REST API Reference | [API.md](API.md) |
|
|
118
|
-
| Error Handling | [ERROR_HANDLING.md](ERROR_HANDLING.md) |
|
|
119
118
|
|
|
120
119
|
## Dependent Projects
|
|
121
120
|
|
package/bin/abapgit-agent
CHANGED
|
@@ -34,32 +34,7 @@ async function main() {
|
|
|
34
34
|
const args = process.argv.slice(2);
|
|
35
35
|
const command = args[0];
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
const commandModules = {
|
|
39
|
-
help: require('../src/commands/help'),
|
|
40
|
-
health: require('../src/commands/health'),
|
|
41
|
-
status: require('../src/commands/status'),
|
|
42
|
-
create: require('../src/commands/create'),
|
|
43
|
-
delete: require('../src/commands/delete'),
|
|
44
|
-
import: require('../src/commands/import'),
|
|
45
|
-
inspect: require('../src/commands/inspect'),
|
|
46
|
-
unit: require('../src/commands/unit'),
|
|
47
|
-
syntax: require('../src/commands/syntax'),
|
|
48
|
-
tree: require('../src/commands/tree'),
|
|
49
|
-
list: require('../src/commands/list'),
|
|
50
|
-
view: require('../src/commands/view'),
|
|
51
|
-
preview: require('../src/commands/preview'),
|
|
52
|
-
where: require('../src/commands/where'),
|
|
53
|
-
dump: require('../src/commands/dump'),
|
|
54
|
-
debug: require('../src/commands/debug'),
|
|
55
|
-
run: require('../src/commands/run'),
|
|
56
|
-
ref: require('../src/commands/ref'),
|
|
57
|
-
guide: require('../src/commands/guide'),
|
|
58
|
-
init: require('../src/commands/init'),
|
|
59
|
-
pull: require('../src/commands/pull'),
|
|
60
|
-
upgrade: require('../src/commands/upgrade'),
|
|
61
|
-
transport: require('../src/commands/transport')
|
|
62
|
-
};
|
|
37
|
+
const commandModules = require('../src/commands/index');
|
|
63
38
|
|
|
64
39
|
// Check if this is a modular command
|
|
65
40
|
if (commandModules[command] || command === '--help' || command === '-h') {
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.2",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
|
-
"main": "src/index.js",
|
|
6
5
|
"files": [
|
|
7
6
|
"bin/",
|
|
8
7
|
"src/",
|
|
@@ -18,8 +17,6 @@
|
|
|
18
17
|
"abgagt": "bin/abgagt"
|
|
19
18
|
},
|
|
20
19
|
"scripts": {
|
|
21
|
-
"start": "node src/server.js",
|
|
22
|
-
"dev": "nodemon src/server.js",
|
|
23
20
|
"test": "jest",
|
|
24
21
|
"test:all": "node tests/run-all.js",
|
|
25
22
|
"test:unit": "jest",
|
|
@@ -51,16 +48,13 @@
|
|
|
51
48
|
"unrelease": "node scripts/unrelease.js"
|
|
52
49
|
},
|
|
53
50
|
"dependencies": {
|
|
54
|
-
"cors": "^2.8.5",
|
|
55
51
|
"dotenv": "^16.3.1",
|
|
56
|
-
"express": "^4.18.2",
|
|
57
52
|
"node-fetch": "^2.7.0",
|
|
58
53
|
"uuid": "^9.0.0",
|
|
59
54
|
"winston": "^3.11.0"
|
|
60
55
|
},
|
|
61
56
|
"devDependencies": {
|
|
62
57
|
"jest": "^29.7.0",
|
|
63
|
-
"nodemon": "^3.0.1",
|
|
64
58
|
"supertest": "^7.2.2"
|
|
65
59
|
},
|
|
66
60
|
"engines": {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* abapgit-agent command registry
|
|
5
|
+
*
|
|
6
|
+
* Each entry maps a CLI command name to its command module.
|
|
7
|
+
* Command modules must export: { name, description, requiresAbapConfig, execute }
|
|
8
|
+
*/
|
|
9
|
+
module.exports = {
|
|
10
|
+
help: require('./help'),
|
|
11
|
+
health: require('./health'),
|
|
12
|
+
status: require('./status'),
|
|
13
|
+
create: require('./create'),
|
|
14
|
+
delete: require('./delete'),
|
|
15
|
+
import: require('./import'),
|
|
16
|
+
inspect: require('./inspect'),
|
|
17
|
+
unit: require('./unit'),
|
|
18
|
+
syntax: require('./syntax'),
|
|
19
|
+
tree: require('./tree'),
|
|
20
|
+
list: require('./list'),
|
|
21
|
+
view: require('./view'),
|
|
22
|
+
preview: require('./preview'),
|
|
23
|
+
where: require('./where'),
|
|
24
|
+
dump: require('./dump'),
|
|
25
|
+
debug: require('./debug'),
|
|
26
|
+
run: require('./run'),
|
|
27
|
+
ref: require('./ref'),
|
|
28
|
+
guide: require('./guide'),
|
|
29
|
+
init: require('./init'),
|
|
30
|
+
pull: require('./pull'),
|
|
31
|
+
upgrade: require('./upgrade'),
|
|
32
|
+
transport: require('./transport')
|
|
33
|
+
};
|
package/src/commands/pull.js
CHANGED
|
@@ -6,11 +6,38 @@ const { printHttpError } = require('../utils/format-error');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const pathModule = require('path');
|
|
8
8
|
|
|
9
|
+
// Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
|
|
10
|
+
function calcWidth(str) {
|
|
11
|
+
if (!str) return 0;
|
|
12
|
+
let width = 0;
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < str.length) {
|
|
15
|
+
const code = str.codePointAt(i);
|
|
16
|
+
if (!code) break;
|
|
17
|
+
// Variation selectors (FE00-FE0F) and ZWJ (200D) take 0 width
|
|
18
|
+
if (code >= 0xFE00 && code <= 0xFE0F) { i += 1; continue; }
|
|
19
|
+
if (code === 0x200D) { i += 1; continue; } // Zero width joiner
|
|
20
|
+
// Emoji and wide characters take 2 cells
|
|
21
|
+
if (code > 0xFFFF) { width += 2; i += 2; } // Skip surrogate pair
|
|
22
|
+
else if (code > 127) { width += 2; i += 1; }
|
|
23
|
+
else { width += 1; i += 1; }
|
|
24
|
+
}
|
|
25
|
+
return width;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pad string to display width
|
|
29
|
+
function padToWidth(str, width) {
|
|
30
|
+
const s = str || '';
|
|
31
|
+
const currentWidth = calcWidth(s);
|
|
32
|
+
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
33
|
+
}
|
|
9
34
|
module.exports = {
|
|
10
35
|
name: 'pull',
|
|
11
36
|
description: 'Pull and activate ABAP objects from git repository',
|
|
12
37
|
requiresAbapConfig: true,
|
|
13
38
|
requiresVersionCheck: true,
|
|
39
|
+
_calcWidth: calcWidth,
|
|
40
|
+
_padToWidth: padToWidth,
|
|
14
41
|
|
|
15
42
|
async execute(args, context) {
|
|
16
43
|
const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards, getConflictSettings, getTransportSettings } = context;
|
|
@@ -281,44 +308,6 @@ module.exports = {
|
|
|
281
308
|
return icons[type] || '';
|
|
282
309
|
};
|
|
283
310
|
|
|
284
|
-
// Calculate display width accounting for emoji (2 cells) vs ASCII (1 cell)
|
|
285
|
-
const calcWidth = (str) => {
|
|
286
|
-
if (!str) return 0;
|
|
287
|
-
let width = 0;
|
|
288
|
-
let i = 0;
|
|
289
|
-
while (i < str.length) {
|
|
290
|
-
const code = str.codePointAt(i);
|
|
291
|
-
if (!code) break;
|
|
292
|
-
// Variation selectors (FE00-FE0F) and ZWJ (200D) take 0 width
|
|
293
|
-
if (code >= 0xFE00 && code <= 0xFE0F) {
|
|
294
|
-
i += 1;
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (code === 0x200D) { // Zero width joiner
|
|
298
|
-
i += 1;
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
// Emoji and wide characters take 2 cells
|
|
302
|
-
if (code > 0xFFFF) {
|
|
303
|
-
width += 2;
|
|
304
|
-
i += 2; // Skip surrogate pair
|
|
305
|
-
} else if (code > 127) {
|
|
306
|
-
width += 2;
|
|
307
|
-
i += 1;
|
|
308
|
-
} else {
|
|
309
|
-
width += 1;
|
|
310
|
-
i += 1;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return width;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Pad string to display width
|
|
317
|
-
const padToWidth = (str, width) => {
|
|
318
|
-
const s = str || '';
|
|
319
|
-
const currentWidth = calcWidth(s);
|
|
320
|
-
return s + ' '.repeat(Math.max(0, width - currentWidth));
|
|
321
|
-
};
|
|
322
311
|
|
|
323
312
|
if (success === 'X' || success === true) {
|
|
324
313
|
console.log(`ā
Pull completed successfully!`);
|
package/src/commands/view.js
CHANGED
|
@@ -167,6 +167,8 @@ module.exports = {
|
|
|
167
167
|
description: 'View ABAP object definitions from ABAP system',
|
|
168
168
|
requiresAbapConfig: true,
|
|
169
169
|
requiresVersionCheck: true,
|
|
170
|
+
_buildMethodLineMap: buildMethodLineMap,
|
|
171
|
+
_findFirstExecutableLine: findFirstExecutableLine,
|
|
170
172
|
|
|
171
173
|
async execute(args, context) {
|
|
172
174
|
const { loadConfig, AbapHttp } = context;
|
package/src/agent.js
DELETED
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ABAP Git Agent - Main agent class
|
|
3
|
-
* Uses AbapHttp for all ABAP communication
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { AbapHttp } = require('./utils/abap-http');
|
|
7
|
-
const { getAbapConfig } = require('./config');
|
|
8
|
-
const logger = require('./logger');
|
|
9
|
-
|
|
10
|
-
class ABAPGitAgent {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.config = getAbapConfig();
|
|
13
|
-
this.http = new AbapHttp(this.config);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Pull repository and activate objects
|
|
18
|
-
* @param {string} repoUrl - Git repository URL
|
|
19
|
-
* @param {string} branch - Branch name (default: main)
|
|
20
|
-
* @param {string} username - Git username (optional)
|
|
21
|
-
* @param {string} password - Git password/token (optional)
|
|
22
|
-
* @param {Array} files - Specific files to pull (optional)
|
|
23
|
-
* @param {string} transportRequest - Transport request number (optional)
|
|
24
|
-
* @returns {object} Pull result with success, job_id, message, error_detail
|
|
25
|
-
*/
|
|
26
|
-
async pull(repoUrl, branch = 'main', username = null, password = null, files = null, transportRequest = null) {
|
|
27
|
-
logger.info('Starting pull operation', { repoUrl, branch, username: !!username, files, transportRequest });
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
31
|
-
|
|
32
|
-
const data = {
|
|
33
|
-
url: repoUrl,
|
|
34
|
-
branch: branch,
|
|
35
|
-
username: username || this.config.gitUsername,
|
|
36
|
-
password: password || this.config.gitPassword
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
if (files && files.length > 0) {
|
|
40
|
-
data.files = files;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (transportRequest) {
|
|
44
|
-
data.transport_request = transportRequest;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/pull', data, { csrfToken });
|
|
48
|
-
|
|
49
|
-
// Return the result directly from ABAP (handle uppercase keys from /UI2/CL_JSON)
|
|
50
|
-
return {
|
|
51
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
52
|
-
job_id: result.JOB_ID || result.job_id,
|
|
53
|
-
message: result.MESSAGE || result.message,
|
|
54
|
-
error_detail: result.ERROR_DETAIL || result.error_detail || null,
|
|
55
|
-
activated_count: result.ACTIVATED_COUNT || result.activated_count || 0,
|
|
56
|
-
failed_count: result.FAILED_COUNT || result.failed_count || 0,
|
|
57
|
-
activated_objects: result.ACTIVATED_OBJECTS || result.activated_objects || [],
|
|
58
|
-
failed_objects: result.FAILED_OBJECTS || result.failed_objects || []
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.error('Pull failed', { error: error.message });
|
|
63
|
-
throw new Error(`Pull failed: ${error.message}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Health check
|
|
69
|
-
* @returns {object} Health status
|
|
70
|
-
*/
|
|
71
|
-
async healthCheck() {
|
|
72
|
-
try {
|
|
73
|
-
const result = await this.http.get('/sap/bc/z_abapgit_agent/health');
|
|
74
|
-
return {
|
|
75
|
-
status: 'healthy',
|
|
76
|
-
abap: 'connected',
|
|
77
|
-
version: result.version || '1.4.0'
|
|
78
|
-
};
|
|
79
|
-
} catch (error) {
|
|
80
|
-
return {
|
|
81
|
-
status: 'unhealthy',
|
|
82
|
-
abap: 'disconnected',
|
|
83
|
-
error: error.message
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Check syntax of an ABAP object (legacy - not used by CLI)
|
|
90
|
-
* @param {string} objectType - ABAP object type (e.g., 'CLAS', 'PROG', 'INTF')
|
|
91
|
-
* @param {string} objectName - ABAP object name
|
|
92
|
-
* @returns {object} Syntax check result with errors (if any)
|
|
93
|
-
*/
|
|
94
|
-
async syntaxCheck(objectType, objectName) {
|
|
95
|
-
logger.info('Starting syntax check', { objectType, objectName });
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
99
|
-
const data = {
|
|
100
|
-
object_type: objectType,
|
|
101
|
-
object_name: objectName
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/syntax-check', data, { csrfToken });
|
|
105
|
-
return {
|
|
106
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
107
|
-
object_type: result.OBJECT_TYPE || result.object_type,
|
|
108
|
-
object_name: result.OBJECT_NAME || result.object_name,
|
|
109
|
-
error_count: result.ERROR_COUNT || result.error_count || 0,
|
|
110
|
-
errors: result.ERRORS || result.errors || []
|
|
111
|
-
};
|
|
112
|
-
} catch (error) {
|
|
113
|
-
logger.error('Syntax check failed', { error: error.message });
|
|
114
|
-
throw new Error(`Syntax check failed: ${error.message}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Check syntax of ABAP source code directly (without pull/activation)
|
|
120
|
-
* Supported types: CLAS, INTF, PROG
|
|
121
|
-
* @param {Array} objects - Array of {type, name, source, locals_def?, locals_imp?}
|
|
122
|
-
* @param {string} uccheck - Unicode check mode ('X' for Standard, '5' for Cloud)
|
|
123
|
-
* @returns {object} Syntax check results with success, results array
|
|
124
|
-
*/
|
|
125
|
-
async syntaxCheckSource(objects, uccheck = 'X') {
|
|
126
|
-
logger.info('Starting source syntax check', { objectCount: objects.length, uccheck });
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
130
|
-
const data = {
|
|
131
|
-
objects: objects,
|
|
132
|
-
uccheck: uccheck
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/syntax', data, { csrfToken });
|
|
136
|
-
const success = result.SUCCESS === 'X' || result.SUCCESS === true ||
|
|
137
|
-
result.success === 'X' || result.success === true;
|
|
138
|
-
const results = result.RESULTS || result.results || [];
|
|
139
|
-
const message = result.MESSAGE || result.message || '';
|
|
140
|
-
|
|
141
|
-
// Normalize results
|
|
142
|
-
const normalizedResults = results.map(r => ({
|
|
143
|
-
object_type: r.OBJECT_TYPE || r.object_type,
|
|
144
|
-
object_name: r.OBJECT_NAME || r.object_name,
|
|
145
|
-
success: r.SUCCESS === 'X' || r.SUCCESS === true || r.success === 'X' || r.success === true,
|
|
146
|
-
error_count: r.ERROR_COUNT || r.error_count || 0,
|
|
147
|
-
errors: r.ERRORS || r.errors || [],
|
|
148
|
-
warnings: r.WARNINGS || r.warnings || [],
|
|
149
|
-
message: r.MESSAGE || r.message || ''
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
success,
|
|
154
|
-
message,
|
|
155
|
-
results: normalizedResults
|
|
156
|
-
};
|
|
157
|
-
} catch (error) {
|
|
158
|
-
logger.error('Source syntax check failed', { error: error.message });
|
|
159
|
-
throw new Error(`Source syntax check failed: ${error.message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Run unit tests for package or objects
|
|
165
|
-
* @param {string} packageName - Package name to run tests for (optional)
|
|
166
|
-
* @param {Array} objects - Array of {object_type, object_name} objects (optional)
|
|
167
|
-
* @returns {object} Unit test results with test_count, passed_count, failed_count, results
|
|
168
|
-
*/
|
|
169
|
-
async unitCheck(packageName = null, objects = []) {
|
|
170
|
-
logger.info('Starting unit tests', { package: packageName, objects });
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
174
|
-
const data = {};
|
|
175
|
-
|
|
176
|
-
if (packageName) {
|
|
177
|
-
data.package = packageName;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (objects && objects.length > 0) {
|
|
181
|
-
data.objects = objects;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/unit', data, { csrfToken });
|
|
185
|
-
return {
|
|
186
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
187
|
-
test_count: result.TEST_COUNT || result.test_count || 0,
|
|
188
|
-
passed_count: result.PASSED_COUNT || result.passed_count || 0,
|
|
189
|
-
failed_count: result.FAILED_COUNT || result.failed_count || 0,
|
|
190
|
-
message: result.MESSAGE || result.message || '',
|
|
191
|
-
errors: result.ERRORS || result.errors || []
|
|
192
|
-
};
|
|
193
|
-
} catch (error) {
|
|
194
|
-
logger.error('Unit tests failed', { error: error.message });
|
|
195
|
-
throw new Error(`Unit tests failed: ${error.message}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Create a new online repository
|
|
201
|
-
* @param {string} repoUrl - Git repository URL
|
|
202
|
-
* @param {string} packageName - ABAP package name
|
|
203
|
-
* @param {string} branch - Branch name (default: 'main')
|
|
204
|
-
* @param {string} displayName - Display name for the repository (optional)
|
|
205
|
-
* @param {string} name - Repository name (optional)
|
|
206
|
-
* @param {string} folderLogic - Folder logic: 'PREFIX' or 'FULL' (default: 'PREFIX')
|
|
207
|
-
* @returns {object} Create result with success, repo_key, repo_name, message
|
|
208
|
-
*/
|
|
209
|
-
async create(repoUrl, packageName, branch = 'main', displayName = null, name = null, folderLogic = 'PREFIX') {
|
|
210
|
-
logger.info('Creating repository', { repoUrl, packageName, branch });
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
214
|
-
const data = {
|
|
215
|
-
url: repoUrl,
|
|
216
|
-
package: packageName,
|
|
217
|
-
branch: branch
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
if (displayName) {
|
|
221
|
-
data.display_name = displayName;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (name) {
|
|
225
|
-
data.name = name;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (folderLogic) {
|
|
229
|
-
data.folder_logic = folderLogic;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/create', data, { csrfToken });
|
|
233
|
-
return {
|
|
234
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
235
|
-
repo_key: result.REPO_KEY || result.repo_key,
|
|
236
|
-
repo_name: result.REPO_NAME || result.repo_name,
|
|
237
|
-
message: result.MESSAGE || result.message || '',
|
|
238
|
-
error: result.ERROR || result.error || null
|
|
239
|
-
};
|
|
240
|
-
} catch (error) {
|
|
241
|
-
logger.error('Create failed', { error: error.message });
|
|
242
|
-
throw new Error(`Create failed: ${error.message}`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Import existing objects from package to git repository
|
|
248
|
-
* @param {string} repoUrl - Git repository URL
|
|
249
|
-
* @param {string} message - Commit message (optional)
|
|
250
|
-
* @returns {object} Import result with success, files_staged, commit_sha
|
|
251
|
-
*/
|
|
252
|
-
async import(repoUrl, message = null) {
|
|
253
|
-
logger.info('Starting import operation', { repoUrl, message });
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
257
|
-
const data = {
|
|
258
|
-
url: repoUrl
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
if (message) {
|
|
262
|
-
data.message = message;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/import', data, { csrfToken });
|
|
266
|
-
return {
|
|
267
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
268
|
-
files_staged: result.FILES_STAGED || result.files_staged || 0,
|
|
269
|
-
commit_sha: result.COMMIT_SHA || result.commit_sha || '',
|
|
270
|
-
message: result.MESSAGE || result.message || '',
|
|
271
|
-
error: result.ERROR || result.error || null
|
|
272
|
-
};
|
|
273
|
-
} catch (error) {
|
|
274
|
-
logger.error('Import failed', { error: error.message });
|
|
275
|
-
throw new Error(`Import failed: ${error.message}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Get package hierarchy tree
|
|
281
|
-
* @param {string} packageName - ABAP package name
|
|
282
|
-
* @param {number} depth - Maximum depth to traverse (default: 3)
|
|
283
|
-
* @param {boolean} includeObjects - Include object counts breakdown
|
|
284
|
-
* @returns {object} Tree result with hierarchy, summary, and metadata
|
|
285
|
-
*/
|
|
286
|
-
async tree(packageName, depth = 3, includeObjects = false) {
|
|
287
|
-
logger.info('Getting package tree', { package: packageName, depth, includeObjects });
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
291
|
-
const data = {
|
|
292
|
-
package: packageName,
|
|
293
|
-
depth: Math.min(Math.max(1, depth), 10),
|
|
294
|
-
include_objects: includeObjects
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/tree', data, { csrfToken });
|
|
298
|
-
return {
|
|
299
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
300
|
-
command: result.COMMAND || result.command || 'TREE',
|
|
301
|
-
package: result.PACKAGE || result.package,
|
|
302
|
-
message: result.MESSAGE || result.message || '',
|
|
303
|
-
hierarchy: result.HIERARCHY || result.hierarchy || null,
|
|
304
|
-
summary: result.SUMMARY || result.summary || null,
|
|
305
|
-
error: result.ERROR || result.error || null
|
|
306
|
-
};
|
|
307
|
-
} catch (error) {
|
|
308
|
-
logger.error('Tree command failed', { error: error.message });
|
|
309
|
-
throw new Error(`Tree command failed: ${error.message}`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Preview data from ABAP tables or CDS views
|
|
315
|
-
* @param {Array} objects - Array of table/view names
|
|
316
|
-
* @param {string} type - Object type (TABL, DDLS, etc.)
|
|
317
|
-
* @param {number} limit - Maximum rows to return
|
|
318
|
-
* @param {number} offset - Number of rows to skip
|
|
319
|
-
* @param {string} where - WHERE clause filter
|
|
320
|
-
* @param {Array} columns - Array of column names to display
|
|
321
|
-
* @returns {object} Preview result with rows, fields, and metadata
|
|
322
|
-
*/
|
|
323
|
-
async preview(objects, type = null, limit = 100, offset = 0, where = null, columns = null) {
|
|
324
|
-
logger.info('Previewing data', { objects, type, limit, offset, where, columns });
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
328
|
-
const data = {
|
|
329
|
-
objects: objects,
|
|
330
|
-
limit: Math.min(Math.max(1, limit), 500),
|
|
331
|
-
offset: Math.max(0, offset)
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
if (type) {
|
|
335
|
-
data.type = type;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (where) {
|
|
339
|
-
// Convert ISO date format (YYYY-MM-DD) to ABAP DATS format (YYYYMMDD)
|
|
340
|
-
data.where = this.convertDatesInWhereClause(where);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (columns) {
|
|
344
|
-
data.columns = columns;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/preview', data, { csrfToken });
|
|
348
|
-
return {
|
|
349
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
350
|
-
command: result.COMMAND || result.command || 'PREVIEW',
|
|
351
|
-
message: result.MESSAGE || result.message || '',
|
|
352
|
-
objects: result.OBJECTS || result.objects || [],
|
|
353
|
-
summary: result.SUMMARY || result.summary || null,
|
|
354
|
-
pagination: result.PAGINATION || result.pagination || null,
|
|
355
|
-
error: result.ERROR || result.error || null
|
|
356
|
-
};
|
|
357
|
-
} catch (error) {
|
|
358
|
-
logger.error('Preview command failed', { error: error.message });
|
|
359
|
-
throw new Error(`Preview command failed: ${error.message}`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Convert ISO date formats to ABAP DATS format in WHERE clause
|
|
365
|
-
* @param {string} whereClause - SQL WHERE clause
|
|
366
|
-
* @returns {string} - WHERE clause with dates converted to YYYYMMDD format
|
|
367
|
-
*/
|
|
368
|
-
convertDatesInWhereClause(whereClause) {
|
|
369
|
-
if (!whereClause) return whereClause;
|
|
370
|
-
|
|
371
|
-
const isoDatePattern = /'\\d{4}-\\d{2}-\\d{2}'/g;
|
|
372
|
-
return whereClause.replace(isoDatePattern, (match) => {
|
|
373
|
-
const dateContent = match.slice(1, -1);
|
|
374
|
-
const [year, month, day] = dateContent.split('-');
|
|
375
|
-
return `'${year}${month}${day}'`;
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* List objects in an ABAP package
|
|
381
|
-
* @param {string} packageName - ABAP package name
|
|
382
|
-
* @param {string} type - Comma-separated object types to filter (optional)
|
|
383
|
-
* @param {string} name - Name pattern to filter (optional)
|
|
384
|
-
* @param {number} limit - Maximum number of objects to return (default: 100)
|
|
385
|
-
* @param {number} offset - Offset for pagination (default: 0)
|
|
386
|
-
* @returns {object} List result with objects, by_type, total, and error
|
|
387
|
-
*/
|
|
388
|
-
async list(packageName, type = null, name = null, limit = 100, offset = 0) {
|
|
389
|
-
logger.info('Listing objects', { package: packageName, type, name, limit, offset });
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
393
|
-
const data = {
|
|
394
|
-
package: packageName,
|
|
395
|
-
limit: Math.min(Math.max(1, limit), 1000),
|
|
396
|
-
offset: Math.max(0, offset)
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
if (type) {
|
|
400
|
-
data.type = type;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (name) {
|
|
404
|
-
data.name = name;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/list', data, { csrfToken });
|
|
408
|
-
|
|
409
|
-
// Normalize objects array to use lowercase keys
|
|
410
|
-
const rawObjects = result.OBJECTS || result.objects || [];
|
|
411
|
-
const normalizedObjects = rawObjects.map(obj => ({
|
|
412
|
-
type: obj.TYPE || obj.type,
|
|
413
|
-
name: obj.NAME || obj.name
|
|
414
|
-
}));
|
|
415
|
-
|
|
416
|
-
// Normalize by_type array to use lowercase keys
|
|
417
|
-
const rawByType = result.BY_TYPE || result.by_type || [];
|
|
418
|
-
const normalizedByType = rawByType.map(item => ({
|
|
419
|
-
type: item.TYPE || item.type,
|
|
420
|
-
count: item.COUNT || item.count || 0
|
|
421
|
-
}));
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
425
|
-
command: result.COMMAND || result.command || 'LIST',
|
|
426
|
-
package: result.PACKAGE || result.package || packageName,
|
|
427
|
-
message: result.MESSAGE || result.message || '',
|
|
428
|
-
objects: normalizedObjects,
|
|
429
|
-
by_type: normalizedByType,
|
|
430
|
-
total: result.TOTAL || result.total || 0,
|
|
431
|
-
error: result.ERROR || result.error || null
|
|
432
|
-
};
|
|
433
|
-
} catch (error) {
|
|
434
|
-
logger.error('List command failed', { error: error.message });
|
|
435
|
-
throw new Error(`List command failed: ${error.message}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* View ABAP object definitions
|
|
441
|
-
* @param {Array} objects - Array of object names to view
|
|
442
|
-
* @param {string} type - Object type (optional, e.g., 'CLAS', 'TABL')
|
|
443
|
-
* @returns {object} View result with object definitions
|
|
444
|
-
*/
|
|
445
|
-
async view(objects, type = null) {
|
|
446
|
-
logger.info('Viewing objects', { objects, type });
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
450
|
-
const data = {
|
|
451
|
-
objects: objects
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
if (type) {
|
|
455
|
-
data.type = type;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/view', data, { csrfToken });
|
|
459
|
-
return {
|
|
460
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
461
|
-
command: result.COMMAND || result.command || 'VIEW',
|
|
462
|
-
objects: result.OBJECTS || result.objects || [],
|
|
463
|
-
error: result.ERROR || result.error || null
|
|
464
|
-
};
|
|
465
|
-
} catch (error) {
|
|
466
|
-
logger.error('View command failed', { error: error.message });
|
|
467
|
-
throw new Error(`View command failed: ${error.message}`);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Find where-used list for ABAP objects
|
|
473
|
-
* @param {Array} objects - Array of object names to search
|
|
474
|
-
* @param {string} type - Object type (optional)
|
|
475
|
-
* @param {number} limit - Maximum results (default: 100, max: 500)
|
|
476
|
-
* @param {number} offset - Number of results to skip (default: 0)
|
|
477
|
-
* @returns {object} Where-used result with found objects
|
|
478
|
-
*/
|
|
479
|
-
async where(objects, type = null, limit = 100, offset = 0) {
|
|
480
|
-
logger.info('Finding where-used', { objects, type, limit, offset });
|
|
481
|
-
|
|
482
|
-
try {
|
|
483
|
-
const csrfToken = await this.http.fetchCsrfToken();
|
|
484
|
-
const data = {
|
|
485
|
-
objects: objects,
|
|
486
|
-
limit: Math.min(Math.max(1, limit), 500),
|
|
487
|
-
offset: Math.max(0, offset)
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
if (type) {
|
|
491
|
-
data.type = type;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const result = await this.http.post('/sap/bc/z_abapgit_agent/where-used', data, { csrfToken });
|
|
495
|
-
return {
|
|
496
|
-
success: result.SUCCESS === 'X' || result.success === 'X' || result.success === true,
|
|
497
|
-
command: result.COMMAND || result.command || 'WHERE',
|
|
498
|
-
objects: result.OBJECTS || result.objects || [],
|
|
499
|
-
message: result.MESSAGE || result.message || '',
|
|
500
|
-
pagination: result.PAGINATION || result.pagination || null,
|
|
501
|
-
error: result.ERROR || result.error || null
|
|
502
|
-
};
|
|
503
|
-
} catch (error) {
|
|
504
|
-
logger.error('Where command failed', { error: error.message });
|
|
505
|
-
throw new Error(`Where command failed: ${error.message}`);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
module.exports = {
|
|
511
|
-
ABAPGitAgent
|
|
512
|
-
};
|
package/src/index.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ABAP Git Agent - Package Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Exports functions for programmatic use:
|
|
5
|
-
* const { pull, healthCheck } = require('abapgit-agent');
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { ABAPGitAgent } = require('./agent');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Pull repository and activate objects
|
|
12
|
-
* @param {string} repoUrl - Git repository URL
|
|
13
|
-
* @param {string} branch - Branch name (default: main)
|
|
14
|
-
* @param {string} username - Git username (optional)
|
|
15
|
-
* @param {string} password - Git password/token (optional)
|
|
16
|
-
* @returns {object} Pull result with success, job_id, message, error_detail
|
|
17
|
-
*/
|
|
18
|
-
async function pull(repoUrl, branch = 'main', username = null, password = null) {
|
|
19
|
-
const agent = new ABAPGitAgent();
|
|
20
|
-
return await agent.pull(repoUrl, branch, username, password);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check agent health
|
|
25
|
-
* @returns {object} Health status
|
|
26
|
-
*/
|
|
27
|
-
async function healthCheck() {
|
|
28
|
-
const agent = new ABAPGitAgent();
|
|
29
|
-
return await agent.healthCheck();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check if integration is configured for current directory
|
|
34
|
-
* @returns {boolean} True if .abapGitAgent exists
|
|
35
|
-
*/
|
|
36
|
-
function isConfigured() {
|
|
37
|
-
const fs = require('fs');
|
|
38
|
-
const path = require('path');
|
|
39
|
-
const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
|
|
40
|
-
return fs.existsSync(repoConfigPath);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
pull,
|
|
45
|
-
healthCheck,
|
|
46
|
-
isConfigured,
|
|
47
|
-
ABAPGitAgent
|
|
48
|
-
};
|
package/src/server.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Server for Claude Integration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const express = require('express');
|
|
6
|
-
const cors = require('cors');
|
|
7
|
-
const { ABAPGitAgent } = require('./agent');
|
|
8
|
-
const { getAgentConfig } = require('./config');
|
|
9
|
-
const logger = require('./logger');
|
|
10
|
-
|
|
11
|
-
class Server {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.app = express();
|
|
14
|
-
this.agent = new ABAPGitAgent();
|
|
15
|
-
this.agentConfig = getAgentConfig();
|
|
16
|
-
|
|
17
|
-
this.setupMiddleware();
|
|
18
|
-
this.setupRoutes();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
setupMiddleware() {
|
|
22
|
-
this.app.use(cors());
|
|
23
|
-
this.app.use(express.json());
|
|
24
|
-
|
|
25
|
-
// Request logging
|
|
26
|
-
this.app.use((req, res, next) => {
|
|
27
|
-
logger.debug(`${req.method} ${req.path}`, { body: req.body });
|
|
28
|
-
next();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Error handling
|
|
32
|
-
this.app.use((err, req, res, next) => {
|
|
33
|
-
logger.error('Request error', { error: err.message, stack: err.stack });
|
|
34
|
-
res.status(500).json({
|
|
35
|
-
success: false,
|
|
36
|
-
error: err.message
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
setupRoutes() {
|
|
42
|
-
// Health check
|
|
43
|
-
this.app.get('/api/health', async (req, res) => {
|
|
44
|
-
try {
|
|
45
|
-
const health = await this.agent.healthCheck();
|
|
46
|
-
res.json(health);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
res.status(503).json({
|
|
49
|
-
status: 'unhealthy',
|
|
50
|
-
error: error.message
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Pull repository (synchronous - returns immediately with result)
|
|
56
|
-
this.app.post('/api/pull', async (req, res) => {
|
|
57
|
-
try {
|
|
58
|
-
const { url, branch, username, password } = req.body;
|
|
59
|
-
|
|
60
|
-
if (!url) {
|
|
61
|
-
return res.status(400).json({
|
|
62
|
-
success: false,
|
|
63
|
-
error: 'Missing required parameter: url'
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const result = await this.agent.pull(url, branch, username, password);
|
|
68
|
-
res.json(result);
|
|
69
|
-
|
|
70
|
-
} catch (error) {
|
|
71
|
-
logger.error('Pull failed', { error: error.message });
|
|
72
|
-
res.status(500).json({
|
|
73
|
-
success: false,
|
|
74
|
-
error: error.message
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
start() {
|
|
81
|
-
const port = this.agentConfig.port || 3000;
|
|
82
|
-
|
|
83
|
-
this.server = this.app.listen(port, () => {
|
|
84
|
-
logger.info(`ABAP AI Bridge server started on port ${port}`);
|
|
85
|
-
console.log(`\nš ABAP AI Bridge is running!`);
|
|
86
|
-
console.log(` Health: http://localhost:${port}/api/health`);
|
|
87
|
-
console.log(` Pull: POST http://localhost:${port}/api/pull`);
|
|
88
|
-
console.log(`\nš API Documentation:`);
|
|
89
|
-
console.log(` POST /api/pull { "url": "git-url", "branch": "main" }`);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Graceful shutdown
|
|
93
|
-
process.on('SIGTERM', () => this.shutdown());
|
|
94
|
-
process.on('SIGINT', () => this.shutdown());
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
shutdown() {
|
|
98
|
-
logger.info('Shutting down server...');
|
|
99
|
-
if (this.server) {
|
|
100
|
-
this.server.close(() => {
|
|
101
|
-
logger.info('Server closed');
|
|
102
|
-
process.exit(0);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Start server if run directly
|
|
109
|
-
if (require.main === module) {
|
|
110
|
-
const server = new Server();
|
|
111
|
-
server.start();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = {
|
|
115
|
-
Server
|
|
116
|
-
};
|