abapgit-agent 1.13.7 → 1.14.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 CHANGED
@@ -97,14 +97,17 @@ abapgit-agent health # Verify ABAP connection
97
97
 
98
98
  ```bash
99
99
  # Install dependencies
100
- cd abapgit-agent
101
100
  npm install
102
101
 
103
- # Run from package directory (auto-detects from git)
104
- node bin/abapgit-agent pull
102
+ # Run unit tests (no ABAP system needed)
103
+ npm test
105
104
 
106
- # Or use npm script
107
- npm run pull -- --url <git-url> --branch main
105
+ # Test a command manually
106
+ node bin/abapgit-agent --help
107
+ node bin/abapgit-agent syntax --files src/zcl_my_class.clas.abap
108
+
109
+ # Run integration tests against a real ABAP system (requires .abapGitAgent)
110
+ npm run test:integration
108
111
  ```
109
112
 
110
113
  ## Documentation
package/abap/CLAUDE.md CHANGED
@@ -471,7 +471,7 @@ Run abaplint as step 4b — after `syntax`, before `git commit`:
471
471
 
472
472
  ```bash
473
473
  # Only if .abaplint.json exists
474
- npx @abaplint/cli .abaplint.json
474
+ abapgit-agent lint
475
475
  ```
476
476
 
477
477
  Fix any reported issues, then commit.
@@ -539,6 +539,21 @@ See **AI Tool Guidelines** below for how to react to each setting.
539
539
  ### Branch Workflow (`"mode": "branch"`)
540
540
 
541
541
  Always work on feature branches. Before every `pull`: rebase to default branch. On completion: create PR with squash merge.
542
+
543
+ ```bash
544
+ git checkout main # or master/develop (auto-detected)
545
+ git pull origin main
546
+ git checkout -b feature/my-change
547
+ # edit your ABAP file (name from objects.local.md)
548
+ abapgit-agent syntax --files src/<name>.clas.abap
549
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
550
+ git add . && git commit -m "feat: description"
551
+ git push origin feature/my-change
552
+ git fetch origin main && git rebase origin/main
553
+ git push origin feature/my-change --force-with-lease
554
+ abapgit-agent pull --files src/<name>.clas.abap --sync-xml
555
+ ```
556
+
542
557
  → See `guidelines/branch-workflow.md` — run: `abapgit-agent ref --topic branch-workflow`
543
558
 
544
559
  ### Trunk Workflow (`"mode": "trunk"`)
@@ -550,6 +565,7 @@ git checkout main # or master/develop (auto-detected)
550
565
  git pull origin main
551
566
  # edit your ABAP file (name from objects.local.md)
552
567
  abapgit-agent syntax --files src/<name>.clas.abap
568
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
553
569
  git add . && git commit -m "feat: description"
554
570
  git push origin main
555
571
  abapgit-agent pull --files src/<name>.clas.abap --sync-xml
@@ -649,7 +665,7 @@ Modified ABAP files?
649
665
  └─ FUGR and other complex objects?
650
666
  └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --sync-xml → (if errors: inspect)
651
667
 
652
- [abaplint] = run npx @abaplint/cli .abaplint.json only if .abaplint.json exists in repo root
668
+ [abaplint] = run abapgit-agent lint only if .abaplint.json exists in repo root
653
669
  before applying any quickfix: run abapgit-agent ref --topic abaplint
654
670
  ```
655
671
 
@@ -9,7 +9,9 @@ grand_parent: ABAP Development
9
9
  # abaplint Rule Guidelines
10
10
 
11
11
  **Searchable keywords**: abaplint, prefer_inline, inline declaration, char literal, string truncation,
12
- no_inline_in_optional_branches, fully_type_constants, linting, static analysis
12
+ no_inline_in_optional_branches, fully_type_constants, linting, static analysis,
13
+ run abaplint locally, check changed file, abapgit-agent lint,
14
+ keyword_case, sequential_blank, double_space, use_new, local_variable_names
13
15
 
14
16
  This file covers rules that have **non-obvious or dangerous implications** — cases where applying
15
17
  a rule mechanically (or accepting its quickfix) can introduce subtle bugs.
@@ -166,8 +168,57 @@ on rv_result — no intermediate lv_response variable at all.
166
168
 
167
169
  ---
168
170
 
171
+ ## Running abaplint Locally Against Changed Files
172
+
173
+ Run this before pushing to catch issues early, matching what CI does.
174
+
175
+ ```bash
176
+ abapgit-agent lint
177
+ ```
178
+
179
+ This automatically detects changed `.abap` files (via `git diff`), creates a scoped
180
+ abaplint config for just those files, runs the check, and cleans up.
181
+
182
+ ### Options
183
+
184
+ ```bash
185
+ # Diff against a specific base branch (useful on a feature branch)
186
+ abapgit-agent lint --base main
187
+
188
+ # Check specific files explicitly
189
+ abapgit-agent lint --files src/zcl_foo.clas.abap,src/zcl_foo.clas.testclasses.abap
190
+
191
+ # Use a different abaplint config (default: .abaplint.json)
192
+ abapgit-agent lint --config .abaplint.json
193
+ ```
194
+
195
+ Run repeatedly after each fix until you see:
196
+
197
+ ```
198
+ abaplint: 0 issue(s) found, 1 file(s) analyzed
199
+ ```
200
+
201
+ ---
202
+
203
+ ### Common Issues and Fixes
204
+
205
+ | Rule | Error message | Fix |
206
+ |------|--------------|-----|
207
+ | `keyword_case` | `Keyword should be upper case: "class"` | Uppercase the keyword: `CLASS` |
208
+ | `sequential_blank` | `Remove sequential blank lines` | Max 1 blank line between blocks |
209
+ | `local_variable_names` | `<fs_data> does not match pattern` | Use `l`-prefixed name: `<ls_data>` |
210
+ | `double_space` | `Remove double space` | Single space around `=` in parameters |
211
+ | `use_new` | `Use NEW #() to instantiate` | Replace `CREATE OBJECT mo_foo` → `mo_foo = NEW #( )` |
212
+ | `method_parameter_names` | `Parameter name does not match pattern` | Use `iv_`, `it_`, `is_`, `io_` etc. prefixes |
213
+
214
+ See **abaplint-local.md** for the full naming convention prefix reference.
215
+
216
+ ---
217
+
218
+
169
219
  ## See Also
170
220
 
171
221
  - **common-errors.md** — char-literal truncation listed as a known error pattern
172
222
  - **json.md** — safe patterns for building JSON strings in ABAP
173
223
  - **workflow-detailed.md** — where abaplint fits in the development workflow
224
+ - **abaplint-local.md** — naming convention reference (prefixes for variables, parameters, field-symbols)
@@ -21,6 +21,7 @@ edit src/zcl_auth_handler.clas.abap
21
21
 
22
22
  # 3. Check syntax (CLAS/INTF/PROG/DDLS only, if independent)
23
23
  abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
24
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
24
25
 
25
26
  # 4. Commit
26
27
  git add src/zcl_auth_handler.clas.abap
@@ -112,6 +113,7 @@ git checkout main && git pull origin main
112
113
  git checkout -b feature/user-authentication
113
114
  edit src/zcl_auth_handler.clas.abap
114
115
  abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
116
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
115
117
  git add . && git commit -m "wip: add basic auth logic"
116
118
  git push origin feature/user-authentication
117
119
  git fetch origin main && git rebase origin/main
@@ -123,6 +125,8 @@ git fetch origin main && git rebase origin/main
123
125
  # If conflicts: resolve, git add, git rebase --continue
124
126
  git push origin feature/user-authentication --force-with-lease
125
127
  edit src/zcl_auth_handler.clas.abap
128
+ abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
129
+ ls .abaplint.json 2>/dev/null && abapgit-agent lint # abaplint (if configured)
126
130
  git add . && git commit -m "feat: complete auth logic"
127
131
  git push origin feature/user-authentication
128
132
  git fetch origin main && git rebase origin/main
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.13.7",
3
+ "version": "1.14.0",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -29,5 +29,6 @@ module.exports = {
29
29
  init: require('./init'),
30
30
  pull: require('./pull'),
31
31
  upgrade: require('./upgrade'),
32
- transport: require('./transport')
32
+ transport: require('./transport'),
33
+ lint: require('./lint')
33
34
  };
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * lint command - Run abaplint on changed ABAP files
5
+ *
6
+ * Detects files changed relative to a base branch (or HEAD~1),
7
+ * creates a scoped abaplint config for just those files, and runs the check.
8
+ *
9
+ * Usage:
10
+ * abapgit-agent lint
11
+ * abapgit-agent lint --config .abaplint.json
12
+ * abapgit-agent lint --base main
13
+ * abapgit-agent lint --files src/foo.clas.abap,src/foo.clas.testclasses.abap
14
+ * abapgit-agent lint --outformat checkstyle --outfile reports/abaplint-results.xml
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execSync, spawnSync } = require('child_process');
20
+
21
+ module.exports = {
22
+ name: 'lint',
23
+ description: 'Run abaplint on changed ABAP files',
24
+ requiresAbapConfig: false,
25
+
26
+ execute(args) {
27
+ const configPath = argValue(args, '--config') || '.abaplint.json';
28
+ const baseBranch = argValue(args, '--base');
29
+ const filesArg = argValue(args, '--files');
30
+ const outformat = argValue(args, '--outformat');
31
+ const outfile = argValue(args, '--outfile');
32
+
33
+ // ── Resolve changed files ─────────────────────────────────────────────────
34
+ let abapFiles;
35
+ if (filesArg) {
36
+ abapFiles = filesArg.split(',').map(f => f.trim()).filter(f => f.endsWith('.abap'));
37
+ } else {
38
+ abapFiles = detectChangedAbapFiles(baseBranch);
39
+ }
40
+
41
+ if (abapFiles.length === 0) {
42
+ console.log('No changed .abap files found — nothing to lint.');
43
+ return;
44
+ }
45
+
46
+ if (!outfile) {
47
+ console.log(`\nLinting ${abapFiles.length} file(s):`);
48
+ abapFiles.forEach(f => console.log(` ${f}`));
49
+ console.log('');
50
+ }
51
+
52
+ // ── Load and scope the abaplint config ────────────────────────────────────
53
+ if (!fs.existsSync(configPath)) {
54
+ console.error(`Error: abaplint config not found: ${configPath}`);
55
+ console.error('Run from the project root, or pass --config <path>.');
56
+ process.exit(1);
57
+ }
58
+
59
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
60
+ cfg.global.files = abapFiles.map(f => `/${f}`);
61
+
62
+ const scopedConfig = '.abaplint-local.json';
63
+ fs.writeFileSync(scopedConfig, JSON.stringify(cfg, null, 2));
64
+
65
+ // ── Run abaplint ──────────────────────────────────────────────────────────
66
+ try {
67
+ const formatArgs = outformat ? `--outformat ${outformat}` : '';
68
+ const fileArgs = outfile ? `--outfile ${outfile}` : '';
69
+ const result = spawnSync(
70
+ `npx @abaplint/cli@latest ${scopedConfig} ${formatArgs} ${fileArgs}`,
71
+ { stdio: 'inherit', shell: true }
72
+ );
73
+ if (result.status !== 0) {
74
+ process.exitCode = result.status;
75
+ }
76
+ } finally {
77
+ fs.unlinkSync(scopedConfig);
78
+ }
79
+ }
80
+ };
81
+
82
+ // ── Helpers ───────────────────────────────────────────────────────────────────
83
+
84
+ function argValue(args, flag) {
85
+ const idx = args.indexOf(flag);
86
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
87
+ }
88
+
89
+ /**
90
+ * Detect changed .abap files using git diff.
91
+ * - If on a PR branch (CHANGE_TARGET set, e.g. in CI): diffs against that target.
92
+ * - If --base is given: diffs against that branch.
93
+ * - Otherwise: diffs HEAD~1..HEAD (last commit).
94
+ */
95
+ function detectChangedAbapFiles(baseBranch) {
96
+ const base = baseBranch
97
+ || (process.env.CHANGE_TARGET ? `origin/${process.env.CHANGE_TARGET}` : null);
98
+
99
+ let diffCmd;
100
+ if (base) {
101
+ diffCmd = `git diff --name-only ${base}...HEAD -- '*.abap'`;
102
+ } else {
103
+ // Fall back to uncommitted changes first, then last commit
104
+ const uncommitted = runGit('git diff --name-only HEAD -- *.abap').filter(Boolean);
105
+ if (uncommitted.length > 0) return filterAbapFiles(uncommitted);
106
+ diffCmd = `git diff --name-only HEAD~1 HEAD -- '*.abap'`;
107
+ }
108
+
109
+ return filterAbapFiles(runGit(diffCmd));
110
+ }
111
+
112
+ function runGit(cmd) {
113
+ try {
114
+ return execSync(cmd, { encoding: 'utf8' }).trim().split('\n').filter(Boolean);
115
+ } catch {
116
+ return [];
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Keep only files that look like ABAP source files
122
+ * (name.type.abap or name.type.subtype.abap).
123
+ */
124
+ function filterAbapFiles(files) {
125
+ return files.filter(f => {
126
+ const parts = path.basename(f).split('.');
127
+ return (parts.length === 3 || parts.length === 4) &&
128
+ parts[parts.length - 1].toLowerCase() === 'abap';
129
+ });
130
+ }