abapgit-agent 1.17.1 → 1.17.3

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.
@@ -8,5 +8,12 @@
8
8
  "protocol": "https",
9
9
  "gitUsername": "github-username",
10
10
  "gitPassword": "github-token",
11
- "referenceFolder": "~/abap-reference"
11
+ "referenceFolder": "~/abap-reference",
12
+
13
+ "testRepos": {
14
+ "pull": "https://github.com/your-org/abgagt-pull-test.git",
15
+ "drop": "https://github.com/your-org/abgagt-drop-test.git",
16
+ "customize": "https://github.com/your-org/abgagt-customize-test.git",
17
+ "lifecycle": "https://github.com/your-org/abgagt-lifecycle-test.git"
18
+ }
12
19
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "project": {
3
+ "name": "MY_PACKAGE",
4
+ "description": ""
5
+ },
6
+
7
+ "safeguards": {
8
+ "requireFilesForPull": false,
9
+ "disablePull": false,
10
+ "disableRun": false,
11
+ "disableImport": false,
12
+ "requireImportMessage": false,
13
+ "disableProbeClasses": false
14
+ },
15
+
16
+ "conflictDetection": {
17
+ "mode": "abort"
18
+ }
19
+ }
package/README.md CHANGED
@@ -45,7 +45,7 @@ abapgit-agent import
45
45
  abapgit-agent pull
46
46
  ```
47
47
 
48
- See [Creating New ABAP Projects](INSTALL.md#creating-new-abap-projects) to set up a new ABAP repository with Claude Code integration.
48
+ See [Creating New ABAP Projects](docs/install.md#creating-new-abap-projects) to set up a new ABAP repository with Claude Code integration.
49
49
 
50
50
  ## CLI Commands
51
51
 
@@ -99,25 +99,38 @@ abapgit-agent health # Verify ABAP connection
99
99
  # Install dependencies
100
100
  npm install
101
101
 
102
- # Run unit tests (no ABAP system needed)
103
- npm test
104
-
105
102
  # Test a command manually
106
103
  node bin/abapgit-agent --help
107
104
  node bin/abapgit-agent syntax --files src/zcl_my_class.clas.abap
105
+ ```
108
106
 
109
- # Run integration tests against a real ABAP system (requires .abapGitAgent)
110
- npm run test:integration
107
+ ## Running Tests
108
+
109
+ ```bash
110
+ # Unit tests — no ABAP system needed (fast)
111
+ npm test
112
+
113
+ # Integration tests — requires a configured .abapGitAgent
114
+ npm run test:setup # one-time setup: clone test repos + activate ABAP objects
115
+ npm run test:all # full suite (setup runs automatically on first run)
116
+
117
+ # Run a single suite
118
+ npm run test:cmd # CLI command tests
119
+ npm run test:drop # drop command tests
120
+ npm run test:customize # customize command tests
121
+ npm run test:aunit # ABAP unit tests
111
122
  ```
112
123
 
124
+ > **Full integration test guide:** [docs/integration-tests.md](docs/integration-tests.md)
125
+
113
126
  ## Documentation
114
127
 
115
128
  | Topic | File |
116
129
  |-------|------|
117
130
  | Full Documentation | [https://sylvoscai.github.io/abapgit-agent/](https://sylvoscai.github.io/abapgit-agent/) |
118
- | Installation & Setup | [INSTALL.md](INSTALL.md) |
131
+ | Installation & Setup | [docs/install.md](docs/install.md) |
119
132
  | All Commands Overview | [docs/commands.md](docs/commands.md) |
120
- | REST API Reference | [API.md](API.md) |
133
+ | REST API Reference | [docs/api.md](docs/api.md) |
121
134
 
122
135
  ## Dependent Projects
123
136
 
@@ -63,5 +63,14 @@ User asks to create a probe class
63
63
 
64
64
  ### When `disableProbeClasses = true` and `scratchWorkspace` is NOT configured
65
65
 
66
- Refuse and tell the user to configure `scratchWorkspace` in `.abapGitAgent`.
67
- → See `docs/run-command.md` (Scratch Workspace section) for setup instructions.
66
+ Refuse and tell the user to configure `scratchWorkspace` in `.abapGitAgent`:
67
+
68
+ ```json
69
+ {
70
+ "scratchWorkspace": {
71
+ "path": "/absolute/path/to/scratch-repo"
72
+ }
73
+ }
74
+ ```
75
+
76
+ The path must point to a separate git repo initialized with `abapgit-agent init --package <SCRATCH_PACKAGE>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.17.1",
3
+ "version": "1.17.3",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -10,7 +10,8 @@
10
10
  "abap/CLAUDE.slim.md",
11
11
  "abap/.github/copilot-instructions.md",
12
12
  "abap/.github/copilot-instructions.slim.md",
13
- ".abapGitAgent.example"
13
+ ".abapGitAgent.example",
14
+ ".abapgit-agent.example.json"
14
15
  ],
15
16
  "bin": {
16
17
  "abapgit-agent": "bin/abapgit-agent",
@@ -19,6 +20,7 @@
19
20
  "scripts": {
20
21
  "test": "jest",
21
22
  "test:all": "node tests/run-all.js",
23
+ "test:setup": "node tests/run-all.js --setup",
22
24
  "test:unit": "jest",
23
25
  "test:jest": "jest",
24
26
  "test:integration": "node tests/run-all.js --cmd",
@@ -129,7 +129,7 @@ Usage:
129
129
 
130
130
  Description:
131
131
  Initialize local repository configuration.
132
- Creates .abapGitAgent, .abapgit.xml, .gitignore, CLAUDE.md, and guidelines folder.
132
+ Creates .abapGitAgent, .abapgit-agent.json, .abapgit.xml, .gitignore, CLAUDE.md, and guidelines folder.
133
133
 
134
134
  Options:
135
135
  --package <PACKAGE> ABAP package name (required)
@@ -562,11 +562,32 @@ Uncomment and edit the rows that differ from the defaults in \`guidelines/object
562
562
  console.log(`✅ .abapgit.xml already exists, skipped`);
563
563
  }
564
564
 
565
+ // Create .abapgit-agent.json with default values (team config, checked into git)
566
+ const projectConfigPath = pathModule.join(process.cwd(), '.abapgit-agent.json');
567
+ if (!fs.existsSync(projectConfigPath)) {
568
+ const projectConfigSamplePath = pathModule.join(__dirname, '..', '..', '.abapgit-agent.example.json');
569
+ if (!fs.existsSync(projectConfigSamplePath)) {
570
+ console.error('Error: .abapgit-agent.example.json not found.');
571
+ process.exit(1);
572
+ }
573
+ try {
574
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigSamplePath, 'utf8'));
575
+ projectConfig.project.name = packageName;
576
+ fs.writeFileSync(projectConfigPath, JSON.stringify(projectConfig, null, 2) + '\n');
577
+ console.log(`✅ Created .abapgit-agent.json (team config — commit this to git)`);
578
+ } catch (error) {
579
+ console.error(`Error creating .abapgit-agent.json: ${error.message}`);
580
+ }
581
+ } else {
582
+ console.log(`✅ .abapgit-agent.json already exists, skipped`);
583
+ }
584
+
565
585
  console.log(`
566
586
  📋 Next steps:
567
587
  1. Edit .abapGitAgent with your ABAP credentials (host, user, password)
568
- 2. Run 'abapgit-agent create --import' to create online repository
569
- 3. Run 'abapgit-agent pull' to activate objects
588
+ 2. Review .abapgit-agent.json and commit it to git (team settings)
589
+ 3. Run 'abapgit-agent create --import' to create online repository
590
+ 4. Run 'abapgit-agent pull' to activate objects
570
591
 
571
592
  💡 Tips:
572
593
  • Only guidelines/objects.local.md needs to live in your repo.
@@ -59,13 +59,13 @@ Examples:
59
59
  // ── Resolve changed files ─────────────────────────────────────────────────
60
60
  let abapFiles;
61
61
  if (filesArg) {
62
- abapFiles = filesArg.split(',').map(f => f.trim()).filter(f => f.endsWith('.abap'));
62
+ abapFiles = filesArg.split(',').map(f => f.trim()).filter(f => isLintable(f) && fs.existsSync(f));
63
63
  } else {
64
64
  abapFiles = detectChangedAbapFiles(baseBranch);
65
65
  }
66
66
 
67
67
  if (abapFiles.length === 0) {
68
- console.log('No changed .abap files found — nothing to lint.');
68
+ console.log('No changed .abap/.asddls files found — nothing to lint.');
69
69
  return;
70
70
  }
71
71
 
@@ -102,34 +102,29 @@ Examples:
102
102
  // keep <file> blocks for the originally changed files — suppressing any
103
103
  // pre-existing issues in dependency files that were not part of this change.
104
104
  try {
105
- if (outformat === 'checkstyle') {
106
- // Run to a temp file, filter, then write to the final destination.
107
- const tempOut = '.abaplint-raw.xml';
108
- const abapFilesSet = new Set(abapFiles.map(f => path.resolve(f)));
109
- try {
110
- const result = spawnSync(
111
- `npx @abaplint/cli@latest ${scopedConfig} --outformat checkstyle --outfile ${tempOut}`,
112
- { stdio: 'pipe', shell: true }
113
- );
114
- const raw = fs.existsSync(tempOut) ? fs.readFileSync(tempOut, 'utf8') : '<checkstyle version="8.0"/>';
115
- const filtered = filterCheckstyleToFiles(raw, abapFilesSet);
105
+ const tempOut = '.abaplint-raw.xml';
106
+ const abapFilesSet = new Set(abapFiles.map(f => path.resolve(f)));
107
+ try {
108
+ spawnSync(
109
+ `npx @abaplint/cli@latest ${scopedConfig} --outformat checkstyle --outfile ${tempOut}`,
110
+ { stdio: 'pipe', shell: true }
111
+ );
112
+ const raw = fs.existsSync(tempOut) ? fs.readFileSync(tempOut, 'utf8') : '<checkstyle version="8.0"/>';
113
+ const filtered = filterCheckstyleToFiles(raw, abapFilesSet);
114
+ if (outformat === 'checkstyle') {
116
115
  if (outfile) {
117
116
  fs.writeFileSync(outfile, filtered);
118
117
  } else {
119
118
  process.stdout.write(filtered);
120
119
  }
121
- const issueCount = (filtered.match(/<error /g) || []).length;
122
- if (issueCount > 0) process.exitCode = 1;
123
- } finally {
124
- if (fs.existsSync(tempOut)) fs.unlinkSync(tempOut);
120
+ } else {
121
+ // Interactive: print issues as human-readable text, scoped to changed files only.
122
+ printCheckstyleAsText(filtered);
125
123
  }
126
- } else {
127
- // Interactive: inherit stdio so abaplint's human-readable output flows through.
128
- const result = spawnSync(
129
- `npx @abaplint/cli@latest ${scopedConfig}`,
130
- { stdio: 'inherit', shell: true }
131
- );
132
- if (result.status !== 0) process.exitCode = result.status;
124
+ const issueCount = (filtered.match(/<error /g) || []).length;
125
+ if (issueCount > 0) process.exitCode = 1;
126
+ } finally {
127
+ if (fs.existsSync(tempOut)) fs.unlinkSync(tempOut);
133
128
  }
134
129
  } finally {
135
130
  fs.unlinkSync(scopedConfig);
@@ -156,12 +151,12 @@ function detectChangedAbapFiles(baseBranch) {
156
151
 
157
152
  let diffCmd;
158
153
  if (base) {
159
- diffCmd = `git diff --name-only ${base}...HEAD -- '*.abap'`;
154
+ diffCmd = `git diff --name-only ${base}...HEAD -- '*.abap' '*.asddls'`;
160
155
  } else {
161
156
  // Fall back to uncommitted changes first, then last commit
162
- const uncommitted = runGit('git diff --name-only HEAD -- *.abap').filter(Boolean);
157
+ const uncommitted = runGit(`git diff --name-only HEAD -- '*.abap' '*.asddls'`).filter(Boolean);
163
158
  if (uncommitted.length > 0) return filterAbapFiles(uncommitted);
164
- diffCmd = `git diff --name-only HEAD~1 HEAD -- '*.abap'`;
159
+ diffCmd = `git diff --name-only HEAD~1 HEAD -- '*.abap' '*.asddls'`;
165
160
  }
166
161
 
167
162
  return filterAbapFiles(runGit(diffCmd));
@@ -190,7 +185,7 @@ function buildFileIndex(abapDir) {
190
185
  const full = path.join(dir, entry.name);
191
186
  if (entry.isDirectory()) {
192
187
  walk(full);
193
- } else if (entry.name.endsWith('.abap') || entry.name.endsWith('.xml')) {
188
+ } else if (entry.name.endsWith('.abap') || entry.name.endsWith('.xml') || entry.name.endsWith('.asddls')) {
194
189
  index.set(entry.name.toLowerCase(), full);
195
190
  }
196
191
  }
@@ -304,13 +299,47 @@ function filterCheckstyleToFiles(xml, abapFilesSet) {
304
299
  }
305
300
 
306
301
  /**
307
- * Keep only files that look like ABAP source files
308
- * (name.type.abap or name.type.subtype.abap).
302
+ * Print checkstyle XML as human-readable text, mirroring abaplint's default output format:
303
+ * path/to/file.clas.abap:line - severity - message (rule)
304
+ */
305
+ function printCheckstyleAsText(xml) {
306
+ const fileRe = /<file\s+name="([^"]*)"([\s\S]*?)<\/file>/g;
307
+ const errorRe = /<error\s+[^>]*line="(\d+)"[^>]*severity="([^"]*)"[^>]*message="([^"]*)"[^>]*source="([^"]*)"/g;
308
+ let fileMatch;
309
+ let issueCount = 0;
310
+ while ((fileMatch = fileRe.exec(xml)) !== null) {
311
+ const filePath = fileMatch[1];
312
+ const block = fileMatch[2];
313
+ errorRe.lastIndex = 0;
314
+ let errMatch;
315
+ while ((errMatch = errorRe.exec(block)) !== null) {
316
+ const [, line, severity, message, source] = errMatch;
317
+ const rule = source.split('.').pop();
318
+ const text = message.replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&#39;/g, "'");
319
+ console.log(`${filePath}:${line} - ${severity} - ${text} (${rule})`);
320
+ issueCount++;
321
+ }
322
+ }
323
+ if (issueCount === 0) {
324
+ console.log('No issues found.');
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Returns true if the file is a type abaplint can analyse.
330
+ * - .abap — all ABAP source files (CLAS, INTF, PROG, FUGR, ENHO, etc.)
331
+ * - .asddls — CDS view / view entity sources (DDLS)
332
+ */
333
+ function isLintable(f) {
334
+ const lower = f.toLowerCase();
335
+ return lower.endsWith('.abap') || lower.endsWith('.asddls');
336
+ }
337
+
338
+ /**
339
+ * Keep only lintable files (name.type.abap / name.type.subtype.abap / name.ddls.asddls)
340
+ * that still exist on disk. Deleted files appear in git diff output but must not
341
+ * be passed to abaplint.
309
342
  */
310
343
  function filterAbapFiles(files) {
311
- return files.filter(f => {
312
- const parts = path.basename(f).split('.');
313
- return (parts.length === 3 || parts.length === 4) &&
314
- parts[parts.length - 1].toLowerCase() === 'abap';
315
- });
344
+ return files.filter(f => isLintable(f) && fs.existsSync(f));
316
345
  }
@@ -215,4 +215,4 @@ npm test -- tests/unit/backgroundJobPoller.test.js
215
215
 
216
216
  - [Background Job Architecture](../../docs/architecture/README.md) - Complete architecture documentation
217
217
  - [Import Command](../commands/import.js) - Reference implementation
218
- - [API Documentation](../../API.md) - REST API for async commands
218
+ - [API Documentation](../../docs/api.md) - REST API for async commands