abapgit-agent 1.7.2 ā 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/README.md +7 -7
- package/abap/CLAUDE.md +145 -26
- 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 -2852
- package/package.json +21 -5
- package/src/agent.js +205 -16
- 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/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 -526
- /package/src/{ref-search.js ā utils/abap-reference.js} +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import command - Import existing objects from package to git repository
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'import',
|
|
7
|
+
description: 'Import existing objects from package to git repository',
|
|
8
|
+
requiresAbapConfig: true,
|
|
9
|
+
requiresVersionCheck: true,
|
|
10
|
+
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const { loadConfig, gitUtils, AbapHttp } = context;
|
|
13
|
+
|
|
14
|
+
// Show help if requested
|
|
15
|
+
const helpIndex = args.findIndex(a => a === '--help' || a === '-h');
|
|
16
|
+
if (helpIndex !== -1) {
|
|
17
|
+
console.log(`
|
|
18
|
+
Usage:
|
|
19
|
+
abapgit-agent import [--message <message>]
|
|
20
|
+
|
|
21
|
+
Description:
|
|
22
|
+
Import existing objects from package to git repository.
|
|
23
|
+
Uses the git remote URL to find the abapGit online repository.
|
|
24
|
+
|
|
25
|
+
Prerequisites:
|
|
26
|
+
- Run "abapgit-agent create" first or create repository in abapGit UI
|
|
27
|
+
- Package must have objects to import
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--message Commit message (default: "feat: initial import from ABAP package <package>")
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
abapgit-agent import
|
|
34
|
+
abapgit-agent import --message "Initial import from SAP"
|
|
35
|
+
`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get parameters from config
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
const repoUrl = gitUtils.getRemoteUrl();
|
|
42
|
+
|
|
43
|
+
if (!repoUrl) {
|
|
44
|
+
console.error('Error: No git remote configured. Please configure a remote origin.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const messageArgIndex = args.indexOf('--message');
|
|
49
|
+
let commitMessage = null;
|
|
50
|
+
if (messageArgIndex !== -1 && messageArgIndex + 1 < args.length) {
|
|
51
|
+
commitMessage = args[messageArgIndex + 1];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`\nš¦ Importing objects to git repository`);
|
|
55
|
+
console.log(` URL: ${repoUrl}`);
|
|
56
|
+
if (commitMessage) {
|
|
57
|
+
console.log(` Message: ${commitMessage}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const http = new AbapHttp(config);
|
|
61
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
62
|
+
|
|
63
|
+
const data = {
|
|
64
|
+
url: repoUrl
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (commitMessage) {
|
|
68
|
+
data.message = commitMessage;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (config.gitUsername) {
|
|
72
|
+
data.username = config.gitUsername;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (config.gitPassword) {
|
|
76
|
+
data.password = config.gitPassword;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = await http.post('/sap/bc/z_abapgit_agent/import', data, { csrfToken });
|
|
80
|
+
|
|
81
|
+
console.log('\n');
|
|
82
|
+
|
|
83
|
+
// Handle uppercase keys from ABAP
|
|
84
|
+
const success = result.SUCCESS || result.success;
|
|
85
|
+
const filesStaged = result.FILES_STAGED || result.files_staged;
|
|
86
|
+
const abapCommitMessage = result.COMMIT_MESSAGE || result.commit_message;
|
|
87
|
+
const error = result.ERROR || result.error;
|
|
88
|
+
|
|
89
|
+
if (success === 'X' || success === true) {
|
|
90
|
+
console.log(`ā
Objects imported successfully!`);
|
|
91
|
+
console.log(` Files staged: ${filesStaged}`);
|
|
92
|
+
console.log(` Commit: ${commitMessage || abapCommitMessage}`);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`ā Import failed`);
|
|
95
|
+
console.log(` Error: ${error || 'Unknown error'}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init command - Initialize local repository configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const pathModule = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Copy a file if source exists (helper for init --update)
|
|
10
|
+
*/
|
|
11
|
+
async function copyFileIfExists(srcPath, destPath, label, createParentDir = false) {
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(srcPath)) {
|
|
14
|
+
if (createParentDir) {
|
|
15
|
+
const parentDir = pathModule.dirname(destPath);
|
|
16
|
+
if (!fs.existsSync(parentDir)) {
|
|
17
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
fs.copyFileSync(srcPath, destPath);
|
|
21
|
+
console.log(`ā
Updated ${label}`);
|
|
22
|
+
} else {
|
|
23
|
+
console.log(`ā ļø ${label} not found in abapgit-agent`);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error copying ${label}: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Copy guidelines folder (helper for init --update)
|
|
32
|
+
*/
|
|
33
|
+
async function copyGuidelinesFolder(srcPath, destPath, overwrite = false) {
|
|
34
|
+
try {
|
|
35
|
+
if (fs.existsSync(srcPath)) {
|
|
36
|
+
// Create destination directory if needed
|
|
37
|
+
if (!fs.existsSync(destPath)) {
|
|
38
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files = fs.readdirSync(srcPath);
|
|
42
|
+
let copied = 0;
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
if (file.endsWith('.md')) {
|
|
46
|
+
const srcFile = pathModule.join(srcPath, file);
|
|
47
|
+
const destFile = pathModule.join(destPath, file);
|
|
48
|
+
|
|
49
|
+
// Skip if file exists and not overwriting
|
|
50
|
+
if (fs.existsSync(destFile) && !overwrite) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fs.copyFileSync(srcFile, destFile);
|
|
55
|
+
copied++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (copied > 0) {
|
|
60
|
+
console.log(`ā
Updated guidelines/ (${copied} files)`);
|
|
61
|
+
} else {
|
|
62
|
+
console.log(`ā ļø No guideline files found`);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`ā ļø guidelines folder not found in abapgit-agent`);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`Error copying guidelines: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
name: 'init',
|
|
74
|
+
description: 'Initialize local repository configuration',
|
|
75
|
+
requiresAbapConfig: false,
|
|
76
|
+
requiresVersionCheck: false,
|
|
77
|
+
|
|
78
|
+
async execute(args, context) {
|
|
79
|
+
const { gitUtils } = context;
|
|
80
|
+
|
|
81
|
+
const folderArgIndex = args.indexOf('--folder');
|
|
82
|
+
const packageArgIndex = args.indexOf('--package');
|
|
83
|
+
const updateMode = args.includes('--update');
|
|
84
|
+
|
|
85
|
+
// Get parameters
|
|
86
|
+
let folder = folderArgIndex !== -1 && folderArgIndex + 1 < args.length
|
|
87
|
+
? args[folderArgIndex + 1]
|
|
88
|
+
: '/src/';
|
|
89
|
+
|
|
90
|
+
// Normalize folder path: ensure it starts with / and ends with /
|
|
91
|
+
folder = folder.trim();
|
|
92
|
+
if (!folder.startsWith('/')) {
|
|
93
|
+
folder = '/' + folder;
|
|
94
|
+
}
|
|
95
|
+
if (!folder.endsWith('/')) {
|
|
96
|
+
folder = folder + '/';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const packageName = packageArgIndex !== -1 && packageArgIndex + 1 < args.length
|
|
100
|
+
? args[packageArgIndex + 1]
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
// Validate package is required for non-update mode
|
|
104
|
+
if (!updateMode && !packageName) {
|
|
105
|
+
console.error('Error: --package is required');
|
|
106
|
+
console.error('Usage: abapgit-agent init --folder /src --package ZMY_PACKAGE');
|
|
107
|
+
console.error(' abapgit-agent init --update # Update files to latest version');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if current folder is git repo root
|
|
112
|
+
const gitDir = pathModule.join(process.cwd(), '.git');
|
|
113
|
+
if (!fs.existsSync(gitDir)) {
|
|
114
|
+
console.error('Error: Current folder is not a git repository.');
|
|
115
|
+
console.error('Run this command from the root folder of your git repository.');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// In update mode, just copy the files and return
|
|
120
|
+
if (updateMode) {
|
|
121
|
+
console.log(`\nš Updating abapGit Agent files`);
|
|
122
|
+
console.log('');
|
|
123
|
+
|
|
124
|
+
// Copy CLAUDE.md
|
|
125
|
+
await copyFileIfExists(
|
|
126
|
+
pathModule.join(__dirname, '..', '..', 'abap', 'CLAUDE.md'),
|
|
127
|
+
pathModule.join(process.cwd(), 'CLAUDE.md'),
|
|
128
|
+
'CLAUDE.md'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Copy copilot-instructions.md
|
|
132
|
+
await copyFileIfExists(
|
|
133
|
+
pathModule.join(__dirname, '..', '..', 'abap', '.github', 'copilot-instructions.md'),
|
|
134
|
+
pathModule.join(process.cwd(), '.github', 'copilot-instructions.md'),
|
|
135
|
+
'.github/copilot-instructions.md',
|
|
136
|
+
true // create parent dir
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Copy guidelines folder to project root
|
|
140
|
+
await copyGuidelinesFolder(
|
|
141
|
+
pathModule.join(__dirname, '..', '..', 'abap', 'guidelines'),
|
|
142
|
+
pathModule.join(process.cwd(), 'guidelines'),
|
|
143
|
+
true // overwrite
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
console.log(`
|
|
147
|
+
š Update complete!
|
|
148
|
+
Run 'abapgit-agent ref --list-topics' to see available topics.
|
|
149
|
+
`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate package is required
|
|
154
|
+
if (!packageName) {
|
|
155
|
+
console.error('Error: --package is required');
|
|
156
|
+
console.error('Usage: abapgit-agent init --folder /src --package ZMY_PACKAGE');
|
|
157
|
+
console.error(' abapgit-agent init --update # Update files to latest version');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`\nš Initializing abapGit Agent for local repository`);
|
|
162
|
+
console.log(` Folder: ${folder}`);
|
|
163
|
+
console.log(` Package: ${packageName}`);
|
|
164
|
+
console.log('');
|
|
165
|
+
|
|
166
|
+
// Detect git remote URL
|
|
167
|
+
const gitUrl = gitUtils.getRemoteUrl();
|
|
168
|
+
if (!gitUrl) {
|
|
169
|
+
console.error('Error: No git remote configured.');
|
|
170
|
+
console.error('Configure a remote with: git remote add origin <url>');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
console.log(`š Git remote: ${gitUrl}`);
|
|
174
|
+
|
|
175
|
+
// Check if .abapGitAgent already exists
|
|
176
|
+
const configPath = pathModule.join(process.cwd(), '.abapGitAgent');
|
|
177
|
+
if (fs.existsSync(configPath)) {
|
|
178
|
+
console.error('Error: .abapGitAgent already exists.');
|
|
179
|
+
console.error('To reinitialize, delete the existing file first.');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Copy .abapGitAgent.example to .abapGitAgent
|
|
184
|
+
const samplePath = pathModule.join(__dirname, '..', '..', '.abapGitAgent.example');
|
|
185
|
+
if (!fs.existsSync(samplePath)) {
|
|
186
|
+
console.error('Error: .abapGitAgent.example not found.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// Read sample and update with package/folder
|
|
192
|
+
const sampleContent = fs.readFileSync(samplePath, 'utf8');
|
|
193
|
+
const config = JSON.parse(sampleContent);
|
|
194
|
+
config.package = packageName;
|
|
195
|
+
config.folder = folder;
|
|
196
|
+
|
|
197
|
+
// Write updated config
|
|
198
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
199
|
+
console.log(`ā
Created .abapGitAgent`);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(`Error creating .abapGitAgent: ${error.message}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Update .gitignore
|
|
206
|
+
const gitignorePath = pathModule.join(process.cwd(), '.gitignore');
|
|
207
|
+
const ignoreEntries = ['.abapGitAgent', '.abapgit_agent_cookies.txt'];
|
|
208
|
+
let existingIgnore = '';
|
|
209
|
+
|
|
210
|
+
if (fs.existsSync(gitignorePath)) {
|
|
211
|
+
existingIgnore = fs.readFileSync(gitignorePath, 'utf8');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let updated = false;
|
|
215
|
+
let newIgnoreContent = existingIgnore;
|
|
216
|
+
|
|
217
|
+
for (const entry of ignoreEntries) {
|
|
218
|
+
if (!newIgnoreContent.includes(entry)) {
|
|
219
|
+
if (newIgnoreContent && !newIgnoreContent.endsWith('\n')) {
|
|
220
|
+
newIgnoreContent += '\n';
|
|
221
|
+
}
|
|
222
|
+
newIgnoreContent += entry + '\n';
|
|
223
|
+
updated = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (updated) {
|
|
228
|
+
fs.writeFileSync(gitignorePath, newIgnoreContent);
|
|
229
|
+
console.log(`ā
Updated .gitignore`);
|
|
230
|
+
} else {
|
|
231
|
+
fs.writeFileSync(gitignorePath, newIgnoreContent);
|
|
232
|
+
console.log(`ā
.gitignore already up to date`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Copy CLAUDE.md
|
|
236
|
+
const claudeMdPath = pathModule.join(__dirname, '..', '..', 'abap', 'CLAUDE.md');
|
|
237
|
+
const localClaudeMdPath = pathModule.join(process.cwd(), 'CLAUDE.md');
|
|
238
|
+
try {
|
|
239
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
240
|
+
fs.copyFileSync(claudeMdPath, localClaudeMdPath);
|
|
241
|
+
console.log(`ā
Created CLAUDE.md`);
|
|
242
|
+
} else {
|
|
243
|
+
console.log(`ā ļø CLAUDE.md not found in abap/ directory`);
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error(`Error copying CLAUDE.md: ${error.message}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Copy copilot-instructions.md for GitHub Copilot
|
|
250
|
+
const copilotMdPath = pathModule.join(__dirname, '..', '..', 'abap', '.github', 'copilot-instructions.md');
|
|
251
|
+
const githubDir = pathModule.join(process.cwd(), '.github');
|
|
252
|
+
const localCopilotMdPath = pathModule.join(githubDir, 'copilot-instructions.md');
|
|
253
|
+
try {
|
|
254
|
+
if (fs.existsSync(copilotMdPath)) {
|
|
255
|
+
// Ensure .github directory exists
|
|
256
|
+
if (!fs.existsSync(githubDir)) {
|
|
257
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
258
|
+
}
|
|
259
|
+
fs.copyFileSync(copilotMdPath, localCopilotMdPath);
|
|
260
|
+
console.log(`ā
Created .github/copilot-instructions.md`);
|
|
261
|
+
} else {
|
|
262
|
+
console.log(`ā ļø copilot-instructions.md not found in abap/ directory`);
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error(`Error copying copilot-instructions.md: ${error.message}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Copy guidelines folder to project root
|
|
269
|
+
const guidelinesSrcPath = pathModule.join(__dirname, '..', '..', 'abap', 'guidelines');
|
|
270
|
+
const guidelinesDestPath = pathModule.join(process.cwd(), 'guidelines');
|
|
271
|
+
try {
|
|
272
|
+
if (fs.existsSync(guidelinesSrcPath)) {
|
|
273
|
+
if (!fs.existsSync(guidelinesDestPath)) {
|
|
274
|
+
// Create guidelines directory
|
|
275
|
+
fs.mkdirSync(guidelinesDestPath, { recursive: true });
|
|
276
|
+
// Copy all files from guidelines folder
|
|
277
|
+
const files = fs.readdirSync(guidelinesSrcPath);
|
|
278
|
+
for (const file of files) {
|
|
279
|
+
if (file.endsWith('.md')) {
|
|
280
|
+
fs.copyFileSync(
|
|
281
|
+
pathModule.join(guidelinesSrcPath, file),
|
|
282
|
+
pathModule.join(guidelinesDestPath, file)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
console.log(`ā
Created guidelines/ (${files.filter(f => f.endsWith('.md')).length} files)`);
|
|
287
|
+
} else {
|
|
288
|
+
console.log(`ā ļø guidelines/ already exists, skipped`);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
console.log(`ā ļø guidelines folder not found in abap/ directory`);
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error(`Error copying guidelines: ${error.message}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Create folder
|
|
298
|
+
const folderPath = pathModule.join(process.cwd(), folder);
|
|
299
|
+
try {
|
|
300
|
+
if (!fs.existsSync(folderPath)) {
|
|
301
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
302
|
+
console.log(`ā
Created folder: ${folder}`);
|
|
303
|
+
|
|
304
|
+
// Create .gitkeep
|
|
305
|
+
const gitkeepPath = pathModule.join(folderPath, '.gitkeep');
|
|
306
|
+
fs.writeFileSync(gitkeepPath, '');
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`ā
Folder already exists: ${folder}`);
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error(`Error creating folder: ${error.message}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(`
|
|
315
|
+
š Next steps:
|
|
316
|
+
1. Edit .abapGitAgent with your ABAP credentials (host, user, password)
|
|
317
|
+
2. Run 'abapgit-agent create --import' to create online repository
|
|
318
|
+
3. Run 'abapgit-agent pull' to activate objects
|
|
319
|
+
`);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
@@ -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
|
+
};
|