abapgit-agent 1.11.4 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abap/CLAUDE.md +50 -5
- package/abap/guidelines/debug-session.md +1 -1
- package/abap/guidelines/run-probe-classes.md +67 -0
- package/bin/abapgit-agent +4 -2
- package/package.json +1 -1
- package/src/commands/init.js +29 -1
- package/src/commands/pull.js +56 -20
- package/src/commands/run.js +123 -0
- package/src/config.js +22 -1
package/abap/CLAUDE.md
CHANGED
|
@@ -225,19 +225,50 @@ abapgit-agent unit --files src/zcl_test1.clas.testclasses.abap,src/zcl_test2.cla
|
|
|
225
225
|
|
|
226
226
|
---
|
|
227
227
|
|
|
228
|
-
### 9.
|
|
228
|
+
### 9. Never Run `run` Command Proactively
|
|
229
|
+
|
|
230
|
+
**Never call `abapgit-agent run` unless the user explicitly asks.** A class implementing `IF_OO_ADT_CLASSRUN` can modify data, send emails, or trigger RFCs — running it automatically is unsafe.
|
|
231
|
+
|
|
232
|
+
After activating a class, stop and tell the user: `"Class is activated. Run with: abapgit-agent run --class ZCL_MY_CLASS"`
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### 10. Probe Classes — Use `scratchWorkspace` When Required
|
|
237
|
+
|
|
238
|
+
By default, probe/throwaway classes may be created in the current project. When `disableProbeClasses: true` is set in `.abapgit-agent.json`, they must go to `scratchWorkspace` instead. If `scratchWorkspace` is also not configured, refuse and guide the user to set it up.
|
|
239
|
+
|
|
240
|
+
→ See `guidelines/run-probe-classes.md` for naming conventions, full workflow, and refusal guidance
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 11. Troubleshooting ABAP Issues
|
|
229
245
|
|
|
230
246
|
| Symptom | Tool | When |
|
|
231
247
|
|---|---|---|
|
|
232
248
|
| HTTP 500 / runtime crash (ST22) | `dump` | Error already occurred |
|
|
233
249
|
| Wrong output, no crash | `debug` | Need to trace logic |
|
|
234
250
|
|
|
235
|
-
|
|
251
|
+
→ See `guidelines/debug-dump.md` for dump analysis workflow
|
|
252
|
+
|
|
253
|
+
**Critical rules for `debug` sessions:**
|
|
254
|
+
|
|
255
|
+
1. **Always use `--json`** for all debug commands (`attach`, `vars`, `stack`, `step`) — human output is not machine-parseable
|
|
256
|
+
2. **Attach BEFORE trigger** — start `debug attach --json` in background first, wait for `"Listener active"`, THEN fire the trigger (`unit`/`pull`/`run`)
|
|
257
|
+
3. **Never pull to trigger** if a simpler trigger works — use `unit` when a test exists, `run` for a class runner; use `pull` only when the bug is specifically in the pull flow
|
|
258
|
+
4. **Never pass `--session`** to `step/vars/stack` — it bypasses the daemon and causes errors
|
|
259
|
+
5. **Always finish with `step --type continue --json`** — releases the frozen ABAP work process
|
|
260
|
+
|
|
261
|
+
Minimal correct sequence:
|
|
236
262
|
```bash
|
|
237
|
-
abapgit-agent
|
|
238
|
-
abapgit-agent debug
|
|
263
|
+
abapgit-agent debug set --objects ZCL_FOO:42 # 1. set breakpoint
|
|
264
|
+
abapgit-agent debug attach --json > /tmp/a.json 2>&1 & # 2. attach (background)
|
|
265
|
+
until grep -q "Listener active" /tmp/a.json 2>/dev/null; do sleep 0.3; done
|
|
266
|
+
abapgit-agent unit --files src/zcl_foo.clas.testclasses.abap > /tmp/t.json 2>&1 & # 3. trigger
|
|
267
|
+
# poll for session, then inspect
|
|
268
|
+
abapgit-agent debug vars --json
|
|
269
|
+
abapgit-agent debug step --type continue --json # 4. release
|
|
239
270
|
```
|
|
240
|
-
|
|
271
|
+
|
|
241
272
|
→ See `guidelines/debug-session.md` for full debug session guide, breakpoint tips, and pull flow architecture
|
|
242
273
|
|
|
243
274
|
---
|
|
@@ -326,6 +357,13 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
326
357
|
1. ✗ Do not run `abapgit-agent pull` at all
|
|
327
358
|
2. ✓ Inform the user that pull is disabled for this project (CI/CD only)
|
|
328
359
|
|
|
360
|
+
**When `safeguards.disableRun = true`:**
|
|
361
|
+
1. ✗ Do not run `abapgit-agent run` at all
|
|
362
|
+
2. ✓ Inform the user that run is disabled for this project
|
|
363
|
+
|
|
364
|
+
**When `safeguards.disableProbeClasses = true`:**
|
|
365
|
+
1. ✗ Do not create probe classes in the current project — see Rule 10 and `guidelines/run-command.md`
|
|
366
|
+
|
|
329
367
|
**When `conflictDetection.mode = "ignore"` or not set:**
|
|
330
368
|
1. ✓ Run `pull` normally — no conflict flags needed
|
|
331
369
|
2. ✗ Don't add `--conflict-mode` unless user explicitly asks
|
|
@@ -339,6 +377,12 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
339
377
|
1. ✗ Do not run `abapgit-agent transport create`
|
|
340
378
|
2. ✓ Inform the user that transport creation is disabled for this project
|
|
341
379
|
|
|
380
|
+
**When pull result contains `missing_abapgit_xml: true` (JSON mode) or warning about `.abapgit.xml`:**
|
|
381
|
+
1. ✓ Inform the user that `.abapgit.xml` is missing from the repository root
|
|
382
|
+
2. ✓ Suggest running `abapgit-agent init --package <PACKAGE>` to create it
|
|
383
|
+
3. ✓ If `ACTIVATED_COUNT=0` with an empty log, suspect this as the cause
|
|
384
|
+
4. ✗ Do not retry the pull — fix the missing file first
|
|
385
|
+
|
|
342
386
|
**When `transports.allowRelease = false`:**
|
|
343
387
|
1. ✗ Do not run `abapgit-agent transport release`
|
|
344
388
|
2. ✓ Inform the user that transport release is disabled for this project
|
|
@@ -384,6 +428,7 @@ Detailed guidelines are available in the `guidelines/` folder:
|
|
|
384
428
|
| `guidelines/common-errors.md` | Common ABAP Errors - Quick Fixes |
|
|
385
429
|
| `guidelines/debug-session.md` | Debug Session Guide |
|
|
386
430
|
| `guidelines/debug-dump.md` | Dump Analysis Guide |
|
|
431
|
+
| `guidelines/run-probe-classes.md` | run Command — AI Guidelines (probe classes, scratchWorkspace) |
|
|
387
432
|
| `guidelines/branch-workflow.md` | Branch Workflow |
|
|
388
433
|
| `guidelines/workflow-detailed.md` | Development Workflow (Detailed) |
|
|
389
434
|
| `guidelines/object-creation.md` | Object Creation (XML metadata, local classes) |
|
|
@@ -166,7 +166,7 @@ abapgit-agent debug stack --json # call stack (shows whi
|
|
|
166
166
|
abapgit-agent debug delete --all
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
> **If the stale debug daemon holds an ABAP lock** (symptom: `Requested object EZABAPGIT is currently locked by user
|
|
169
|
+
> **If the stale debug daemon holds an ABAP lock** (symptom: `Requested object EZABAPGIT is currently locked by user <USER>`):
|
|
170
170
|
> ```bash
|
|
171
171
|
> pkill -f "debug-daemon" # kills daemon, SIGTERM triggers session.terminate() internally
|
|
172
172
|
> ```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: run Command Guide
|
|
4
|
+
nav_order: 18
|
|
5
|
+
parent: ABAP Coding Guidelines
|
|
6
|
+
grand_parent: ABAP Development
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# run Command — AI Guidelines
|
|
10
|
+
|
|
11
|
+
## Never Run Proactively
|
|
12
|
+
|
|
13
|
+
`abapgit-agent run` executes live ABAP code. **Never call it unless the user explicitly asks.**
|
|
14
|
+
|
|
15
|
+
A class implementing `IF_OO_ADT_CLASSRUN` can do anything — modify database records, send emails, trigger RFCs. The interface signature gives no indication of side effects.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
User: "Write a class that reads flight data and prints it"
|
|
19
|
+
→ ✓ Create the class, pull it, STOP. Do NOT run it.
|
|
20
|
+
→ ✓ Tell the user: "Class is activated. Run with: abapgit-agent run --class ZCL_MY_CLASS"
|
|
21
|
+
|
|
22
|
+
User: "Now run it"
|
|
23
|
+
→ ✓ Run it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Probe Classes and `scratchWorkspace`
|
|
29
|
+
|
|
30
|
+
### Decision flow
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
User asks to create a probe class
|
|
34
|
+
├── disableProbeClasses = false / not set → create in current project (default)
|
|
35
|
+
└── disableProbeClasses = true
|
|
36
|
+
├── scratchWorkspace configured → create there (see workflow below)
|
|
37
|
+
└── scratchWorkspace not configured → refuse, guide user to set it up
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### When `disableProbeClasses = true` and `scratchWorkspace` is configured
|
|
41
|
+
|
|
42
|
+
**Naming** — derive from `scratchWorkspace` config in `.abapGitAgent`:
|
|
43
|
+
- `classPrefix` (default: `ZCL_{USER}_`) + `<PURPOSE>`, max 30 chars
|
|
44
|
+
- Example: user=`JOHN`, purpose=`OPEN_TRANSPORTS` → `ZCL_JOHN_OPEN_TRANSPORTS`
|
|
45
|
+
- If name already exists in `{path}/src/`, append `_2`, `_3`, etc.
|
|
46
|
+
|
|
47
|
+
**Workflow:**
|
|
48
|
+
1. Read `{path}/.abapGitAgent` to confirm `folder` property (e.g. `/src/`)
|
|
49
|
+
2. Write class files in `{path}/src/`
|
|
50
|
+
3. Commit and push from `{path}`:
|
|
51
|
+
```bash
|
|
52
|
+
cd {path} && git add . && git commit -m "probe: <description>" && git push
|
|
53
|
+
```
|
|
54
|
+
4. Activate:
|
|
55
|
+
```bash
|
|
56
|
+
cd {path} && abapgit-agent pull --files src/<classname>.clas.abap
|
|
57
|
+
```
|
|
58
|
+
5. Tell user (do NOT auto-run):
|
|
59
|
+
```
|
|
60
|
+
Class activated. Run with: abapgit-agent run --class <CLASSNAME>
|
|
61
|
+
```
|
|
62
|
+
Run the command from the original project directory, not `{path}`.
|
|
63
|
+
|
|
64
|
+
### When `disableProbeClasses = true` and `scratchWorkspace` is NOT configured
|
|
65
|
+
|
|
66
|
+
Refuse and tell the user to configure `scratchWorkspace` in `.abapGitAgent`.
|
|
67
|
+
→ See `docs/run-command.md` (Scratch Workspace section) for setup instructions.
|
package/bin/abapgit-agent
CHANGED
|
@@ -24,7 +24,7 @@ const gitUtils = require('../src/utils/git-utils');
|
|
|
24
24
|
const versionCheck = require('../src/utils/version-check');
|
|
25
25
|
const validators = require('../src/utils/validators');
|
|
26
26
|
const { AbapHttp } = require('../src/utils/abap-http');
|
|
27
|
-
const { loadConfig, getTransport, isAbapIntegrationEnabled, getSafeguards, getConflictSettings, getTransportSettings } = require('../src/config');
|
|
27
|
+
const { loadConfig, getTransport, isAbapIntegrationEnabled, getSafeguards, getConflictSettings, getTransportSettings, getScratchWorkspace } = require('../src/config');
|
|
28
28
|
|
|
29
29
|
// Get terminal width for responsive table
|
|
30
30
|
const getTermWidth = () => process.stdout.columns || 80;
|
|
@@ -52,6 +52,7 @@ async function main() {
|
|
|
52
52
|
where: require('../src/commands/where'),
|
|
53
53
|
dump: require('../src/commands/dump'),
|
|
54
54
|
debug: require('../src/commands/debug'),
|
|
55
|
+
run: require('../src/commands/run'),
|
|
55
56
|
ref: require('../src/commands/ref'),
|
|
56
57
|
init: require('../src/commands/init'),
|
|
57
58
|
pull: require('../src/commands/pull'),
|
|
@@ -105,7 +106,8 @@ To enable integration:
|
|
|
105
106
|
getTransport,
|
|
106
107
|
getSafeguards,
|
|
107
108
|
getConflictSettings,
|
|
108
|
-
getTransportSettings
|
|
109
|
+
getTransportSettings,
|
|
110
|
+
getScratchWorkspace
|
|
109
111
|
};
|
|
110
112
|
|
|
111
113
|
// Execute command
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -129,7 +129,7 @@ Usage:
|
|
|
129
129
|
|
|
130
130
|
Description:
|
|
131
131
|
Initialize local repository configuration.
|
|
132
|
-
Creates .abapGitAgent, .gitignore, CLAUDE.md, and guidelines folder.
|
|
132
|
+
Creates .abapGitAgent, .abapgit.xml, .gitignore, CLAUDE.md, and guidelines folder.
|
|
133
133
|
|
|
134
134
|
Options:
|
|
135
135
|
--package <PACKAGE> ABAP package name (required)
|
|
@@ -500,6 +500,34 @@ Uncomment and edit the rows that differ from the defaults in \`guidelines/object
|
|
|
500
500
|
console.error(`Error creating folder: ${error.message}`);
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
// Create .abapgit.xml with correct STARTING_FOLDER so abapGit's remove_ignored_files()
|
|
504
|
+
// keeps files inside the source folder. Without this file the stored starting_folder in
|
|
505
|
+
// the ABAP persistence may not match the actual folder, causing all remote files to be
|
|
506
|
+
// silently ignored and pull to return ACTIVATED_COUNT=0 with an empty log.
|
|
507
|
+
const abapgitXmlPath = pathModule.join(process.cwd(), '.abapgit.xml');
|
|
508
|
+
if (!fs.existsSync(abapgitXmlPath)) {
|
|
509
|
+
const language = (config.language || 'E').toUpperCase().charAt(0);
|
|
510
|
+
const abapgitXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
511
|
+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
|
512
|
+
<asx:values>
|
|
513
|
+
<DATA>
|
|
514
|
+
<MASTER_LANGUAGE>${language}</MASTER_LANGUAGE>
|
|
515
|
+
<STARTING_FOLDER>${folder}</STARTING_FOLDER>
|
|
516
|
+
<FOLDER_LOGIC>${folderLogic}</FOLDER_LOGIC>
|
|
517
|
+
</DATA>
|
|
518
|
+
</asx:values>
|
|
519
|
+
</asx:abap>
|
|
520
|
+
`;
|
|
521
|
+
try {
|
|
522
|
+
fs.writeFileSync(abapgitXmlPath, abapgitXml);
|
|
523
|
+
console.log(`✅ Created .abapgit.xml (STARTING_FOLDER=${folder}, FOLDER_LOGIC=${folderLogic})`);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error(`Error creating .abapgit.xml: ${error.message}`);
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
console.log(`✅ .abapgit.xml already exists, skipped`);
|
|
529
|
+
}
|
|
530
|
+
|
|
503
531
|
console.log(`
|
|
504
532
|
📋 Next steps:
|
|
505
533
|
1. Edit .abapGitAgent with your ABAP credentials (host, user, password)
|
package/src/commands/pull.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { printHttpError } = require('../utils/format-error');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const pathModule = require('path');
|
|
6
8
|
|
|
7
9
|
module.exports = {
|
|
8
10
|
name: 'pull',
|
|
@@ -84,6 +86,20 @@ module.exports = {
|
|
|
84
86
|
console.error(' Example: --files src/zcl_my_class.clas.abap');
|
|
85
87
|
process.exit(1);
|
|
86
88
|
}
|
|
89
|
+
|
|
90
|
+
// Validate that every specified file exists on disk.
|
|
91
|
+
// Skip when --url is explicit: the files belong to a different repository
|
|
92
|
+
// and won't be present in the current working directory.
|
|
93
|
+
if (urlArgIndex === -1) {
|
|
94
|
+
const fs = require('fs');
|
|
95
|
+
const missingFiles = files.filter(f => !fs.existsSync(f));
|
|
96
|
+
if (missingFiles.length > 0) {
|
|
97
|
+
console.error('❌ Error: the following file(s) do not exist:');
|
|
98
|
+
missingFiles.forEach(f => console.error(` ${f}`));
|
|
99
|
+
console.error(' Check the path and try again.');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
// SAFEGUARD 2: Check if files are required but not provided
|
|
@@ -102,22 +118,30 @@ module.exports = {
|
|
|
102
118
|
if (!transportRequest && !jsonOutput) {
|
|
103
119
|
const { selectTransport, isNonInteractive, _getTransportHookConfig } = require('../utils/transport-selector');
|
|
104
120
|
|
|
105
|
-
// Check
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
let transportRequired =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
// Check repository status — confirms the repo is registered and tells us
|
|
122
|
+
// whether a transport is required. If the check fails, abort: pulling into
|
|
123
|
+
// an uninitialised repository does not make sense.
|
|
124
|
+
let transportRequired = false;
|
|
125
|
+
let statusResult;
|
|
126
|
+
try {
|
|
127
|
+
const config = loadConfig();
|
|
128
|
+
const http = new AbapHttp(config);
|
|
129
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
130
|
+
statusResult = await http.post('/sap/bc/z_abapgit_agent/status', { url: gitUrl }, { csrfToken });
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error(`❌ Repository status check failed: ${e.message}`);
|
|
133
|
+
console.error(' Make sure the repository is registered with abapgit-agent (run "abapgit-agent create").');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!statusResult || statusResult.status === 'Not found') {
|
|
138
|
+
console.error(`❌ Repository not found in ABAP system: ${gitUrl}`);
|
|
139
|
+
console.error(' Run "abapgit-agent create" to register it first.');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (statusResult.transport_required === true || statusResult.transport_required === 'true') {
|
|
144
|
+
transportRequired = true;
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
if (transportRequired) {
|
|
@@ -147,10 +171,10 @@ module.exports = {
|
|
|
147
171
|
}
|
|
148
172
|
}
|
|
149
173
|
|
|
150
|
-
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, undefined, conflictMode);
|
|
174
|
+
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, undefined, conflictMode, verbose);
|
|
151
175
|
},
|
|
152
176
|
|
|
153
|
-
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined, conflictMode = 'abort') {
|
|
177
|
+
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined, conflictMode = 'abort', verbose = false) {
|
|
154
178
|
const TERM_WIDTH = process.stdout.columns || 80;
|
|
155
179
|
|
|
156
180
|
if (!jsonOutput) {
|
|
@@ -196,12 +220,26 @@ module.exports = {
|
|
|
196
220
|
|
|
197
221
|
const result = await http.post('/sap/bc/z_abapgit_agent/pull', data, { csrfToken });
|
|
198
222
|
|
|
223
|
+
// Detect missing .abapgit.xml — without it abapGit's stored starting_folder
|
|
224
|
+
// may not match the actual source folder, causing pull to silently return ACTIVATED_COUNT=0
|
|
225
|
+
const missingAbapgitXml = fs.existsSync(pathModule.join(process.cwd(), '.git')) &&
|
|
226
|
+
!fs.existsSync(pathModule.join(process.cwd(), '.abapgit.xml'));
|
|
227
|
+
|
|
199
228
|
// JSON output mode
|
|
200
229
|
if (jsonOutput) {
|
|
230
|
+
if (missingAbapgitXml) {
|
|
231
|
+
result.missing_abapgit_xml = true;
|
|
232
|
+
}
|
|
201
233
|
console.log(JSON.stringify(result, null, 2));
|
|
202
234
|
return result;
|
|
203
235
|
}
|
|
204
236
|
|
|
237
|
+
if (missingAbapgitXml) {
|
|
238
|
+
console.warn('⚠️ .abapgit.xml not found in repository root.');
|
|
239
|
+
console.warn(' Pull may return ACTIVATED_COUNT=0 if the ABAP-side starting_folder is wrong.');
|
|
240
|
+
console.warn(' Run: abapgit-agent init --package <PACKAGE> to create it.\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
205
243
|
console.log('\n');
|
|
206
244
|
|
|
207
245
|
// Display raw result for debugging
|
|
@@ -284,11 +322,9 @@ module.exports = {
|
|
|
284
322
|
|
|
285
323
|
if (success === 'X' || success === true) {
|
|
286
324
|
console.log(`✅ Pull completed successfully!`);
|
|
287
|
-
console.log(` Job ID: ${jobId || 'N/A'}`);
|
|
288
325
|
console.log(` Message: ${message || 'N/A'}`);
|
|
289
326
|
} else {
|
|
290
327
|
console.error(`❌ Pull completed with errors!`);
|
|
291
|
-
console.error(` Job ID: ${jobId || 'N/A'}`);
|
|
292
328
|
console.error(` Message: ${message || 'N/A'}`);
|
|
293
329
|
}
|
|
294
330
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* run command — Execute an ABAP program or class and display its output.
|
|
5
|
+
*
|
|
6
|
+
* Programs (--program):
|
|
7
|
+
* POST /sap/bc/adt/programs/programrun/{name}
|
|
8
|
+
* Content-Type: application/vnd.sap.adt.programs.programRun+xml
|
|
9
|
+
* Accept: text/plain
|
|
10
|
+
* Note: SAP's ADT handler (CL_SEDI_ADT_PROGRAMRUN) calls SUBMIT with no
|
|
11
|
+
* WITH additions — runtime parameters cannot be passed. The program
|
|
12
|
+
* always runs with its coded defaults.
|
|
13
|
+
*
|
|
14
|
+
* Classes (--class):
|
|
15
|
+
* POST /sap/bc/adt/oo/classrun/{name}
|
|
16
|
+
* Accept: text/plain
|
|
17
|
+
* Requires the class to implement IF_OO_ADT_CLASSRUN.
|
|
18
|
+
* out->write() output is returned as plain text.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { AdtHttp } = require('../utils/adt-http');
|
|
22
|
+
|
|
23
|
+
const PROGRAM_RUN_XML =
|
|
24
|
+
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
25
|
+
'<adtprog:programRun xmlns:adtprog="http://www.sap.com/adt/programs/programs"/>';
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
name: 'run',
|
|
29
|
+
description: 'Execute an ABAP program or class (IF_OO_ADT_CLASSRUN) and display its output',
|
|
30
|
+
requiresAbapConfig: true,
|
|
31
|
+
requiresVersionCheck: false,
|
|
32
|
+
|
|
33
|
+
async execute(args, context) {
|
|
34
|
+
const { loadConfig, getSafeguards } = context;
|
|
35
|
+
|
|
36
|
+
// Check project-level safeguards
|
|
37
|
+
const safeguards = getSafeguards();
|
|
38
|
+
if (safeguards.disableRun) {
|
|
39
|
+
console.error('❌ Error: run command is disabled for this project\n');
|
|
40
|
+
if (safeguards.reason) {
|
|
41
|
+
console.error(`Reason: ${safeguards.reason}\n`);
|
|
42
|
+
}
|
|
43
|
+
console.error('The run command has been disabled in .abapgit-agent.json');
|
|
44
|
+
console.error('Please contact the project maintainer to enable it.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse arguments
|
|
49
|
+
let programName = null;
|
|
50
|
+
let className = null;
|
|
51
|
+
let jsonOutput = false;
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < args.length; i++) {
|
|
54
|
+
if (args[i] === '--program' && args[i + 1]) {
|
|
55
|
+
programName = args[++i].toUpperCase();
|
|
56
|
+
} else if (args[i] === '--class' && args[i + 1]) {
|
|
57
|
+
className = args[++i].toUpperCase();
|
|
58
|
+
} else if (args[i] === '--json') {
|
|
59
|
+
jsonOutput = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (programName && className) {
|
|
64
|
+
console.error('Error: --program and --class are mutually exclusive');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!programName && !className) {
|
|
69
|
+
console.error('Error: either --program or --class is required');
|
|
70
|
+
console.error('Usage: abapgit-agent run --program <NAME>');
|
|
71
|
+
console.error(' abapgit-agent run --class <NAME>');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const config = loadConfig();
|
|
76
|
+
const adt = new AdtHttp(config);
|
|
77
|
+
|
|
78
|
+
if (!adt.csrfToken) {
|
|
79
|
+
await adt.fetchCsrfToken();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let urlPath, body, requestOptions, targetName;
|
|
83
|
+
|
|
84
|
+
if (className) {
|
|
85
|
+
targetName = className;
|
|
86
|
+
urlPath = `/sap/bc/adt/oo/classrun/${className}`;
|
|
87
|
+
body = '';
|
|
88
|
+
requestOptions = { accept: 'text/plain' };
|
|
89
|
+
} else {
|
|
90
|
+
targetName = programName;
|
|
91
|
+
urlPath = `/sap/bc/adt/programs/programrun/${programName}`;
|
|
92
|
+
body = PROGRAM_RUN_XML;
|
|
93
|
+
requestOptions = {
|
|
94
|
+
contentType: 'application/vnd.sap.adt.programs.programRun+xml',
|
|
95
|
+
accept: 'text/plain'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await adt.request('POST', urlPath, body, requestOptions);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (jsonOutput) {
|
|
104
|
+
console.log(JSON.stringify({ success: false, target: targetName, error: err.message || String(err) }));
|
|
105
|
+
} else {
|
|
106
|
+
console.error(`Error: ${err.message || err}`);
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const output = (response.body || '').trimEnd();
|
|
112
|
+
|
|
113
|
+
if (jsonOutput) {
|
|
114
|
+
const key = className ? 'class' : 'program';
|
|
115
|
+
console.log(JSON.stringify({ success: true, [key]: targetName, output }));
|
|
116
|
+
} else {
|
|
117
|
+
console.log('\n--- Output ---');
|
|
118
|
+
console.log(output || '(no output)');
|
|
119
|
+
console.log('--------------');
|
|
120
|
+
console.log(`✅ Completed: ${targetName}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
package/src/config.js
CHANGED
|
@@ -122,6 +122,8 @@ function getSafeguards() {
|
|
|
122
122
|
return {
|
|
123
123
|
requireFilesForPull: projectConfig.safeguards.requireFilesForPull === true,
|
|
124
124
|
disablePull: projectConfig.safeguards.disablePull === true,
|
|
125
|
+
disableRun: projectConfig.safeguards.disableRun === true,
|
|
126
|
+
disableProbeClasses: projectConfig.safeguards.disableProbeClasses === true,
|
|
125
127
|
reason: projectConfig.safeguards.reason || null
|
|
126
128
|
};
|
|
127
129
|
}
|
|
@@ -130,6 +132,8 @@ function getSafeguards() {
|
|
|
130
132
|
return {
|
|
131
133
|
requireFilesForPull: false,
|
|
132
134
|
disablePull: false,
|
|
135
|
+
disableRun: false,
|
|
136
|
+
disableProbeClasses: false,
|
|
133
137
|
reason: null
|
|
134
138
|
};
|
|
135
139
|
}
|
|
@@ -199,6 +203,22 @@ function getTransportSettings() {
|
|
|
199
203
|
return { allowCreate: true, allowRelease: true, reason: null };
|
|
200
204
|
}
|
|
201
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Get scratch workspace configuration from personal config (.abapGitAgent)
|
|
208
|
+
* Used by AI to create probe/throwaway ABAP classes outside the current project
|
|
209
|
+
* @returns {{ path: string, classPrefix: string, programPrefix: string }|null}
|
|
210
|
+
*/
|
|
211
|
+
function getScratchWorkspace() {
|
|
212
|
+
const cfg = loadConfig();
|
|
213
|
+
if (!cfg.scratchWorkspace) return null;
|
|
214
|
+
const user = (cfg.user || 'PROBE').toUpperCase();
|
|
215
|
+
return {
|
|
216
|
+
path: cfg.scratchWorkspace.path || null,
|
|
217
|
+
classPrefix: cfg.scratchWorkspace.classPrefix || `ZCL_${user}_`,
|
|
218
|
+
programPrefix: cfg.scratchWorkspace.programPrefix || `Z${user}_`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
202
222
|
module.exports = {
|
|
203
223
|
loadConfig,
|
|
204
224
|
getAbapConfig,
|
|
@@ -211,5 +231,6 @@ module.exports = {
|
|
|
211
231
|
getConflictSettings,
|
|
212
232
|
loadProjectConfig,
|
|
213
233
|
getTransportHookConfig,
|
|
214
|
-
getTransportSettings
|
|
234
|
+
getTransportSettings,
|
|
235
|
+
getScratchWorkspace
|
|
215
236
|
};
|