abapgit-agent 1.10.0 → 1.11.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/abap/CLAUDE.md +71 -35
- package/bin/abapgit-agent +14 -4
- package/package.json +3 -1
- package/src/commands/debug.js +3 -3
- package/src/commands/pull.js +95 -13
- package/src/commands/status.js +2 -0
- package/src/commands/transport.js +290 -0
- package/src/config.js +60 -1
- package/src/utils/adt-http.js +30 -5
- package/src/utils/debug-daemon.js +24 -1
- package/src/utils/debug-repl.js +23 -0
- package/src/utils/debug-session.js +162 -40
- package/src/utils/transport-selector.js +289 -0
package/abap/CLAUDE.md
CHANGED
|
@@ -308,32 +308,26 @@ abapgit-agent debug list # confirm it was registered
|
|
|
308
308
|
|
|
309
309
|
**Step 2 — attach and trigger**
|
|
310
310
|
|
|
311
|
-
|
|
311
|
+
Best practice: individual sequential calls. Once the daemon is running and
|
|
312
|
+
the session is saved to the state file, each `vars/stack/step` command is a
|
|
313
|
+
plain standalone call — no `--session` flag needed.
|
|
312
314
|
|
|
313
|
-
*Interactive (human in a terminal):*
|
|
314
315
|
```bash
|
|
315
|
-
#
|
|
316
|
-
abapgit-agent debug attach
|
|
317
|
-
|
|
318
|
-
# Terminal 2 — trigger (any command that calls the backend)
|
|
319
|
-
abapgit-agent unit --files src/zcl_my_class.clas.testclasses.abap
|
|
320
|
-
abapgit-agent inspect --files src/zcl_my_class.clas.abap
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
*Scripted (AI / automation) — best practice: individual sequential calls:*
|
|
324
|
-
|
|
325
|
-
Once the daemon is running and the session is saved to the state file, each
|
|
326
|
-
`vars/stack/step` command is a plain standalone call — no bash script needed.
|
|
327
|
-
|
|
328
|
-
```bash
|
|
329
|
-
# Step 1: start attach listener in background (spawns a daemon, saves session to state file)
|
|
316
|
+
# Start attach listener in background (spawns a daemon, saves session to state file)
|
|
330
317
|
abapgit-agent debug attach --json > /tmp/attach.json 2>&1 &
|
|
331
|
-
sleep 2
|
|
332
318
|
|
|
333
|
-
#
|
|
319
|
+
# Rule 1: wait for "Listener active" in the output, THEN fire the trigger.
|
|
320
|
+
# attach --json prints "Listener active" to stderr (captured in attach.json) the
|
|
321
|
+
# moment the long-poll POST is about to be sent to ADT. Waiting for this marker
|
|
322
|
+
# is reliable under any system load; a blind sleep may fire the trigger before
|
|
323
|
+
# ADT has a registered listener, causing the breakpoint hit to be missed.
|
|
324
|
+
until grep -q "Listener active" /tmp/attach.json 2>/dev/null; do sleep 0.3; done
|
|
325
|
+
sleep 1 # brief extra window for the POST to reach ADT
|
|
326
|
+
|
|
327
|
+
# Trigger in background — MUST stay alive for the whole session
|
|
334
328
|
abapgit-agent unit --files src/zcl_my_class.clas.testclasses.abap > /tmp/trigger.json 2>&1 &
|
|
335
329
|
|
|
336
|
-
#
|
|
330
|
+
# Poll until breakpoint fires and session JSON appears in attach output
|
|
337
331
|
SESSION=""
|
|
338
332
|
for i in $(seq 1 30); do
|
|
339
333
|
sleep 0.5
|
|
@@ -341,40 +335,29 @@ for i in $(seq 1 30); do
|
|
|
341
335
|
[ -n "$SESSION" ] && break
|
|
342
336
|
done
|
|
343
337
|
|
|
344
|
-
#
|
|
338
|
+
# Inspect and step — each is an individual call, no --session needed
|
|
345
339
|
abapgit-agent debug stack --json
|
|
346
340
|
abapgit-agent debug vars --json
|
|
347
341
|
abapgit-agent debug vars --expand LS_OBJECT --json
|
|
348
342
|
abapgit-agent debug step --type over --json
|
|
349
343
|
abapgit-agent debug vars --json
|
|
350
344
|
|
|
351
|
-
#
|
|
345
|
+
# ALWAYS release the ABAP work process before finishing
|
|
352
346
|
abapgit-agent debug step --type continue --json
|
|
353
347
|
|
|
354
|
-
#
|
|
348
|
+
# Check trigger result
|
|
355
349
|
cat /tmp/trigger.json
|
|
356
350
|
rm -f /tmp/attach.json /tmp/trigger.json
|
|
357
351
|
```
|
|
358
352
|
|
|
359
353
|
> **Four rules for scripted mode:**
|
|
360
|
-
> 1. `
|
|
354
|
+
> 1. Wait for `"Listener active"` in the attach output before firing the trigger — this message is printed to stderr (captured in `attach.json`) the moment the listener POST is about to reach ADT. A blind `sleep` is not reliable under system load.
|
|
361
355
|
> 2. Keep the trigger process alive in the background for the entire session — if it exits, the ABAP work process is released and the session ends
|
|
362
356
|
> 3. Always finish with `step --type continue` — this releases the frozen work process so the trigger can complete normally
|
|
363
357
|
> 4. **Never pass `--session` to `step/vars/stack`** — it bypasses the daemon IPC and causes `noSessionAttached`. Omit it and let commands auto-load from the saved state file.
|
|
364
358
|
|
|
365
359
|
**Step 3 — step through and inspect**
|
|
366
360
|
|
|
367
|
-
*Interactive REPL commands (after `attach` without `--json`):*
|
|
368
|
-
```
|
|
369
|
-
debug> v — show all variables
|
|
370
|
-
debug> x LT_DATA — expand a table or structure
|
|
371
|
-
debug> n — step over
|
|
372
|
-
debug> s — step into
|
|
373
|
-
debug> bt — call stack
|
|
374
|
-
debug> q — detach (program continues); kill — hard abort
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
*Scripted commands (after `attach --json`) — omit `--session`, commands auto-load from state file:*
|
|
378
361
|
```bash
|
|
379
362
|
abapgit-agent debug vars --json # all variables
|
|
380
363
|
abapgit-agent debug vars --name LV_RESULT --json # one variable
|
|
@@ -396,6 +379,24 @@ abapgit-agent debug delete --all
|
|
|
396
379
|
|
|
397
380
|
This project's workflow mode is configured in `.abapGitAgent` under `workflow.mode`.
|
|
398
381
|
|
|
382
|
+
### Project-Level Config (`.abapgit-agent.json`)
|
|
383
|
+
|
|
384
|
+
Checked into the repository — applies to all developers. **Read this file at the start of every session.**
|
|
385
|
+
|
|
386
|
+
| Setting | Values | Default | Effect |
|
|
387
|
+
|---------|--------|---------|--------|
|
|
388
|
+
| `safeguards.requireFilesForPull` | `true`/`false` | `false` | Requires `--files` on every pull |
|
|
389
|
+
| `safeguards.disablePull` | `true`/`false` | `false` | Disables pull entirely (CI/CD-only projects) |
|
|
390
|
+
| `conflictDetection.mode` | `"abort"`/`"ignore"` | `"abort"` | Whether to abort pull on conflict |
|
|
391
|
+
| `transports.hook.path` | string | `null` | Path to JS module that auto-selects a transport for pull |
|
|
392
|
+
| `transports.hook.description` | string | `null` | Optional label shown when the hook runs |
|
|
393
|
+
| `transports.allowCreate` | `true`/`false` | `true` | When `false`, `transport create` is blocked |
|
|
394
|
+
| `transports.allowRelease` | `true`/`false` | `true` | When `false`, `transport release` is blocked |
|
|
395
|
+
|
|
396
|
+
CLI `--conflict-mode` always overrides the project config for a single run.
|
|
397
|
+
|
|
398
|
+
See **AI Tool Guidelines** below for how to react to each setting.
|
|
399
|
+
|
|
399
400
|
### Workflow Modes
|
|
400
401
|
|
|
401
402
|
| Mode | Branch Strategy | Rebase Before Pull | Create PR |
|
|
@@ -571,6 +572,37 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
571
572
|
3. ✓ `git pull origin <default>` before push
|
|
572
573
|
4. ✗ Don't create feature branches
|
|
573
574
|
|
|
575
|
+
**Read `.abapgit-agent.json` to determine project safeguards and conflict detection:**
|
|
576
|
+
|
|
577
|
+
**When `safeguards.requireFilesForPull = true`:**
|
|
578
|
+
1. ✓ Always include `--files` in every `pull` command
|
|
579
|
+
2. ✓ Never run `abapgit-agent pull` without `--files`
|
|
580
|
+
3. ✗ Don't suggest or run a full pull without specifying files
|
|
581
|
+
|
|
582
|
+
**When `safeguards.requireFilesForPull = false` or not set:**
|
|
583
|
+
1. ✓ `--files` is optional — use it for speed, omit for full pull
|
|
584
|
+
|
|
585
|
+
**When `safeguards.disablePull = true`:**
|
|
586
|
+
1. ✗ Do not run `abapgit-agent pull` at all
|
|
587
|
+
2. ✓ Inform the user that pull is disabled for this project (CI/CD only)
|
|
588
|
+
|
|
589
|
+
**When `conflictDetection.mode = "ignore"` or not set:**
|
|
590
|
+
1. ✓ Run `pull` normally — no conflict flags needed
|
|
591
|
+
2. ✗ Don't add `--conflict-mode` unless user explicitly asks
|
|
592
|
+
|
|
593
|
+
**When `conflictDetection.mode = "abort"`:**
|
|
594
|
+
1. ✓ Conflict detection is active — pull aborts if ABAP system was edited since last pull
|
|
595
|
+
2. ✓ If pull is aborted with conflict error, inform user and suggest `--conflict-mode ignore` to override for that run
|
|
596
|
+
3. ✗ Don't silently add `--conflict-mode ignore` — always tell the user about the conflict
|
|
597
|
+
|
|
598
|
+
**When `transports.allowCreate = false`:**
|
|
599
|
+
1. ✗ Do not run `abapgit-agent transport create`
|
|
600
|
+
2. ✓ Inform the user that transport creation is disabled for this project
|
|
601
|
+
|
|
602
|
+
**When `transports.allowRelease = false`:**
|
|
603
|
+
1. ✗ Do not run `abapgit-agent transport release`
|
|
604
|
+
2. ✓ Inform the user that transport release is disabled for this project
|
|
605
|
+
|
|
574
606
|
---
|
|
575
607
|
|
|
576
608
|
## Development Workflow (Detailed)
|
|
@@ -600,6 +632,7 @@ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
|
|
|
600
632
|
│
|
|
601
633
|
▼
|
|
602
634
|
6. Activate → abapgit-agent pull --files src/file.clas.abap
|
|
635
|
+
│ (behaviour depends on .abapgit-agent.json — see AI Tool Guidelines)
|
|
603
636
|
│
|
|
604
637
|
▼
|
|
605
638
|
7. Verify → Check pull output
|
|
@@ -791,6 +824,9 @@ abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zif_intf1.intf.abap,sr
|
|
|
791
824
|
# 2. Pull/activate AFTER pushing to git
|
|
792
825
|
abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
|
|
793
826
|
|
|
827
|
+
# Override conflict detection for a single pull (e.g. deliberate branch switch)
|
|
828
|
+
abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore
|
|
829
|
+
|
|
794
830
|
# 3. Inspect AFTER pull (for errors or unsupported types)
|
|
795
831
|
abapgit-agent inspect --files src/zcl_class1.clas.abap
|
|
796
832
|
|
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 } = require('../src/config');
|
|
27
|
+
const { loadConfig, getTransport, isAbapIntegrationEnabled, getSafeguards, getConflictSettings, getTransportSettings } = require('../src/config');
|
|
28
28
|
|
|
29
29
|
// Get terminal width for responsive table
|
|
30
30
|
const getTermWidth = () => process.stdout.columns || 80;
|
|
@@ -55,7 +55,8 @@ async function main() {
|
|
|
55
55
|
ref: require('../src/commands/ref'),
|
|
56
56
|
init: require('../src/commands/init'),
|
|
57
57
|
pull: require('../src/commands/pull'),
|
|
58
|
-
upgrade: require('../src/commands/upgrade')
|
|
58
|
+
upgrade: require('../src/commands/upgrade'),
|
|
59
|
+
transport: require('../src/commands/transport')
|
|
59
60
|
};
|
|
60
61
|
|
|
61
62
|
// Check if this is a modular command
|
|
@@ -102,7 +103,9 @@ To enable integration:
|
|
|
102
103
|
loadConfig,
|
|
103
104
|
AbapHttp,
|
|
104
105
|
getTransport,
|
|
105
|
-
getSafeguards
|
|
106
|
+
getSafeguards,
|
|
107
|
+
getConflictSettings,
|
|
108
|
+
getTransportSettings
|
|
106
109
|
};
|
|
107
110
|
|
|
108
111
|
// Execute command
|
|
@@ -121,4 +124,11 @@ To enable integration:
|
|
|
121
124
|
process.exit(1);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
main()
|
|
127
|
+
main().catch((err) => {
|
|
128
|
+
// Known CLI errors (e.g. pull conflict) already printed their own output — skip re-printing.
|
|
129
|
+
// For all other unexpected errors, print the message without the stack trace.
|
|
130
|
+
if (!err._isPullError && !err._isTransportError) {
|
|
131
|
+
console.error(err.message || err);
|
|
132
|
+
}
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"test:cmd:upgrade": "node tests/run-all.js --cmd --command=upgrade",
|
|
43
43
|
"test:lifecycle": "node tests/run-all.js --lifecycle",
|
|
44
44
|
"test:pull": "node tests/run-all.js --pull",
|
|
45
|
+
"test:full-pull": "node tests/run-all.js --full-pull",
|
|
46
|
+
"test:conflict": "node tests/run-all.js --conflict",
|
|
45
47
|
"pull": "node bin/abapgit-agent",
|
|
46
48
|
"release": "node scripts/release.js",
|
|
47
49
|
"unrelease": "node scripts/unrelease.js"
|
package/src/commands/debug.js
CHANGED
|
@@ -583,7 +583,7 @@ async function cmdAttach(args, config, adt) {
|
|
|
583
583
|
let firstPoll = true;
|
|
584
584
|
|
|
585
585
|
for (let i = 0; i < (takenOver ? MAX_TAKEOVER_POLLS : MAX_POLLS); i++) {
|
|
586
|
-
if (firstPoll
|
|
586
|
+
if (firstPoll) {
|
|
587
587
|
process.stderr.write(' Listener active — trigger your ABAP program now.\n');
|
|
588
588
|
firstPoll = false;
|
|
589
589
|
}
|
|
@@ -806,7 +806,7 @@ async function cmdStep(args, config, adt) {
|
|
|
806
806
|
process.exit(1);
|
|
807
807
|
}
|
|
808
808
|
if (!resp.ok) {
|
|
809
|
-
console.error(`\n Error: ${resp.error}${resp.statusCode ? ` (HTTP ${resp.statusCode})` : ''}\n`);
|
|
809
|
+
console.error(`\n Error: ${resp.error}${resp.statusCode ? ` (HTTP ${resp.statusCode})` : ''}${resp.body ? '\n Body: ' + resp.body.substring(0, 400) : ''}\n`);
|
|
810
810
|
process.exit(1);
|
|
811
811
|
}
|
|
812
812
|
// continued+finished (or empty position) means the program ran to completion
|
|
@@ -1074,7 +1074,7 @@ async function cmdStack(args, config, adt) {
|
|
|
1074
1074
|
process.exit(1);
|
|
1075
1075
|
}
|
|
1076
1076
|
if (!resp.ok) {
|
|
1077
|
-
console.error(`\n Error: ${resp.error}${resp.statusCode ? ` (HTTP ${resp.statusCode})` : ''}\n`);
|
|
1077
|
+
console.error(`\n Error: ${resp.error}${resp.statusCode ? ` (HTTP ${resp.statusCode})` : ''}${resp.body ? '\n Body: ' + resp.body.substring(0, 400) : ''}\n`);
|
|
1078
1078
|
process.exit(1);
|
|
1079
1079
|
}
|
|
1080
1080
|
const frames = resp.frames;
|
package/src/commands/pull.js
CHANGED
|
@@ -9,7 +9,7 @@ module.exports = {
|
|
|
9
9
|
requiresVersionCheck: true,
|
|
10
10
|
|
|
11
11
|
async execute(args, context) {
|
|
12
|
-
const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards } = context;
|
|
12
|
+
const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards, getConflictSettings, getTransportSettings } = context;
|
|
13
13
|
|
|
14
14
|
// Check project-level safeguards
|
|
15
15
|
const safeguards = getSafeguards();
|
|
@@ -29,14 +29,16 @@ module.exports = {
|
|
|
29
29
|
const branchArgIndex = args.indexOf('--branch');
|
|
30
30
|
const filesArgIndex = args.indexOf('--files');
|
|
31
31
|
const transportArgIndex = args.indexOf('--transport');
|
|
32
|
+
const conflictModeArgIndex = args.indexOf('--conflict-mode');
|
|
32
33
|
const jsonOutput = args.includes('--json');
|
|
33
34
|
|
|
34
35
|
// Auto-detect git remote URL if not provided
|
|
35
36
|
let gitUrl = urlArgIndex !== -1 ? args[urlArgIndex + 1] : null;
|
|
36
37
|
let branch = branchArgIndex !== -1 ? args[branchArgIndex + 1] : gitUtils.getBranch();
|
|
37
38
|
let files = null;
|
|
39
|
+
let conflictMode = conflictModeArgIndex !== -1 ? args[conflictModeArgIndex + 1] : getConflictSettings().mode;
|
|
38
40
|
|
|
39
|
-
// Transport: CLI arg takes priority, then config/environment, then
|
|
41
|
+
// Transport: CLI arg takes priority, then config/environment, then selector
|
|
40
42
|
let transportRequest = null;
|
|
41
43
|
if (transportArgIndex !== -1 && transportArgIndex + 1 < args.length) {
|
|
42
44
|
// Explicit --transport argument
|
|
@@ -46,8 +48,39 @@ module.exports = {
|
|
|
46
48
|
transportRequest = getTransport();
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// Auto-detect URL early (needed for transport_required check)
|
|
52
|
+
if (!gitUrl) {
|
|
53
|
+
gitUrl = gitUtils.getRemoteUrl();
|
|
54
|
+
if (!gitUrl) {
|
|
55
|
+
console.error('Error: Not in a git repository or no remote configured.');
|
|
56
|
+
console.error('Either run from a git repo, or specify --url <git-url>');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
if (!jsonOutput) {
|
|
60
|
+
console.log(`📌 Auto-detected git remote: ${gitUrl}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
if (filesArgIndex !== -1 && filesArgIndex + 1 < args.length) {
|
|
50
65
|
files = args[filesArgIndex + 1].split(',').map(f => f.trim());
|
|
66
|
+
|
|
67
|
+
// Validate that every file has a recognised ABAP source extension
|
|
68
|
+
// (.abap or .asddls) — XML metadata files must NOT be passed here
|
|
69
|
+
const ABAP_SOURCE_EXTS = new Set(['abap', 'asddls']);
|
|
70
|
+
const nonSourceFiles = files.filter(f => {
|
|
71
|
+
const base = f.split('/').pop(); // strip directory
|
|
72
|
+
const parts = base.split('.');
|
|
73
|
+
const ext = parts[parts.length - 1].toLowerCase();
|
|
74
|
+
return parts.length < 3 || !ABAP_SOURCE_EXTS.has(ext);
|
|
75
|
+
});
|
|
76
|
+
if (nonSourceFiles.length > 0) {
|
|
77
|
+
console.error('❌ Error: --files only accepts ABAP source files (.abap, .asddls).');
|
|
78
|
+
console.error(' The following file(s) are not ABAP source files:');
|
|
79
|
+
nonSourceFiles.forEach(f => console.error(` ${f}`));
|
|
80
|
+
console.error(' Tip: pass the source file, not the XML metadata file.');
|
|
81
|
+
console.error(' Example: --files src/zcl_my_class.clas.abap');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
51
84
|
}
|
|
52
85
|
|
|
53
86
|
// SAFEGUARD 2: Check if files are required but not provided
|
|
@@ -62,22 +95,59 @@ module.exports = {
|
|
|
62
95
|
process.exit(1);
|
|
63
96
|
}
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
// Auto-select transport when none configured and not in JSON mode
|
|
99
|
+
if (!transportRequest && !jsonOutput) {
|
|
100
|
+
const { selectTransport, isNonInteractive, _getTransportHookConfig } = require('../utils/transport-selector');
|
|
101
|
+
|
|
102
|
+
// Check if this package requires a transport before showing the interactive
|
|
103
|
+
// picker. Skip the status round-trip in non-interactive mode — selectTransport
|
|
104
|
+
// already returns null there without prompting, so the extra HTTP call is wasted.
|
|
105
|
+
let transportRequired = true; // Safe default: assume transport needed
|
|
106
|
+
if (!isNonInteractive()) {
|
|
107
|
+
try {
|
|
108
|
+
const config = loadConfig();
|
|
109
|
+
const http = new AbapHttp(config);
|
|
110
|
+
const csrfToken = await http.fetchCsrfToken();
|
|
111
|
+
const statusResult = await http.post('/sap/bc/z_abapgit_agent/status', { url: gitUrl }, { csrfToken });
|
|
112
|
+
if (statusResult.transport_required === false || statusResult.transport_required === 'false') {
|
|
113
|
+
transportRequired = false;
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// Status check failed — proceed with selector as safe default
|
|
117
|
+
}
|
|
71
118
|
}
|
|
72
|
-
|
|
73
|
-
|
|
119
|
+
|
|
120
|
+
if (transportRequired) {
|
|
121
|
+
const config = loadConfig();
|
|
122
|
+
const http = new AbapHttp(config);
|
|
123
|
+
transportRequest = await selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings);
|
|
124
|
+
|
|
125
|
+
// If a hook was configured but returned no transport, handle based on context
|
|
126
|
+
if (transportRequest === null) {
|
|
127
|
+
const hookConfig = _getTransportHookConfig();
|
|
128
|
+
if (hookConfig && hookConfig.hook) {
|
|
129
|
+
if (isNonInteractive()) {
|
|
130
|
+
// Non-interactive (AI/CI): fail — a configured hook must return a transport
|
|
131
|
+
console.error('❌ Error: transport hook returned no transport request.');
|
|
132
|
+
console.error(` Hook: ${hookConfig.hook}`);
|
|
133
|
+
if (hookConfig.description) console.error(` ${hookConfig.description}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
} else {
|
|
136
|
+
// Interactive (TTY): warn and fall through to the picker
|
|
137
|
+
process.stderr.write(`⚠️ Transport hook returned no transport request (${hookConfig.hook}).\n`);
|
|
138
|
+
process.stderr.write(' Please select one manually:\n');
|
|
139
|
+
const { interactivePicker } = require('../utils/transport-selector');
|
|
140
|
+
transportRequest = await interactivePicker(http);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
74
144
|
}
|
|
75
145
|
}
|
|
76
146
|
|
|
77
|
-
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput);
|
|
147
|
+
await this.pull(gitUrl, branch, files, transportRequest, loadConfig, AbapHttp, jsonOutput, undefined, conflictMode);
|
|
78
148
|
},
|
|
79
149
|
|
|
80
|
-
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined) {
|
|
150
|
+
async pull(gitUrl, branch = 'main', files = null, transportRequest = null, loadConfig, AbapHttp, jsonOutput = false, gitCredentials = undefined, conflictMode = 'abort') {
|
|
81
151
|
const TERM_WIDTH = process.stdout.columns || 80;
|
|
82
152
|
|
|
83
153
|
if (!jsonOutput) {
|
|
@@ -107,6 +177,7 @@ module.exports = {
|
|
|
107
177
|
const data = {
|
|
108
178
|
url: gitUrl,
|
|
109
179
|
branch: branch,
|
|
180
|
+
conflict_mode: conflictMode,
|
|
110
181
|
...(resolvedCredentials ? { username: resolvedCredentials.username, password: resolvedCredentials.password } : {})
|
|
111
182
|
};
|
|
112
183
|
|
|
@@ -140,12 +211,23 @@ module.exports = {
|
|
|
140
211
|
const jobId = result.JOB_ID || result.job_id;
|
|
141
212
|
const message = result.MESSAGE || result.message;
|
|
142
213
|
const errorDetail = result.ERROR_DETAIL || result.error_detail;
|
|
143
|
-
const transportRequestUsed = result.TRANSPORT_REQUEST || result.transport_request;
|
|
144
214
|
const activatedCount = result.ACTIVATED_COUNT || result.activated_count || 0;
|
|
145
215
|
const failedCount = result.FAILED_COUNT || result.failed_count || 0;
|
|
146
216
|
const logMessages = result.LOG_MESSAGES || result.log_messages || [];
|
|
147
217
|
const activatedObjects = result.ACTIVATED_OBJECTS || result.activated_objects || [];
|
|
148
218
|
const failedObjects = result.FAILED_OBJECTS || result.failed_objects || [];
|
|
219
|
+
const conflictReport = result.CONFLICT_REPORT || result.conflict_report || '';
|
|
220
|
+
const conflictCount = result.CONFLICT_COUNT || result.conflict_count || 0;
|
|
221
|
+
|
|
222
|
+
// --- Conflict report (pull was aborted) ---
|
|
223
|
+
if (conflictCount > 0 && conflictReport) {
|
|
224
|
+
console.error(`⚠️ Pull aborted — ${conflictCount} conflict(s) detected\n`);
|
|
225
|
+
console.error('─'.repeat(TERM_WIDTH));
|
|
226
|
+
console.error(conflictReport.replace(/\\n/g, '\n'));
|
|
227
|
+
const err = new Error(message || `Pull aborted — ${conflictCount} conflict(s) detected`);
|
|
228
|
+
err._isPullError = true;
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
149
231
|
|
|
150
232
|
// Icon mapping for message types
|
|
151
233
|
const getIcon = (type) => {
|
package/src/commands/status.js
CHANGED
|
@@ -32,7 +32,9 @@ module.exports = {
|
|
|
32
32
|
console.log(' Repository: ✅ Created');
|
|
33
33
|
const pkg = result.package || result.PACKAGE || 'N/A';
|
|
34
34
|
const key = result.key || result.KEY || result.REPO_KEY || result.repo_key || 'N/A';
|
|
35
|
+
const transportRequired = result.transport_required === true || result.transport_required === 'true';
|
|
35
36
|
console.log(` Package: ${pkg}`);
|
|
37
|
+
console.log(` Transport required: ${transportRequired ? 'Yes' : 'No'}`);
|
|
36
38
|
console.log(` URL: ${repoUrl}`);
|
|
37
39
|
console.log(` Key: ${key}`);
|
|
38
40
|
} else {
|