abapgit-agent 1.17.2 → 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
@@ -99,17 +99,30 @@ 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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.17.2",
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",
@@ -565,24 +565,14 @@ Uncomment and edit the rows that differ from the defaults in \`guidelines/object
565
565
  // Create .abapgit-agent.json with default values (team config, checked into git)
566
566
  const projectConfigPath = pathModule.join(process.cwd(), '.abapgit-agent.json');
567
567
  if (!fs.existsSync(projectConfigPath)) {
568
- const projectConfig = {
569
- project: {
570
- name: packageName,
571
- description: ''
572
- },
573
- safeguards: {
574
- requireFilesForPull: false,
575
- disablePull: false,
576
- disableRun: false,
577
- disableImport: false,
578
- requireImportMessage: false,
579
- disableProbeClasses: false
580
- },
581
- conflictDetection: {
582
- mode: 'abort'
583
- }
584
- };
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
+ }
585
573
  try {
574
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigSamplePath, 'utf8'));
575
+ projectConfig.project.name = packageName;
586
576
  fs.writeFileSync(projectConfigPath, JSON.stringify(projectConfig, null, 2) + '\n');
587
577
  console.log(`✅ Created .abapgit-agent.json (team config — commit this to git)`);
588
578
  } catch (error) {
@@ -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
  }