abapgit-agent 1.17.2 → 1.17.4
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/.abapGitAgent.example +8 -1
- package/.abapgit-agent.example.json +19 -0
- package/README.md +19 -5
- package/package.json +4 -2
- package/src/commands/customize.js +25 -0
- package/src/commands/import.js +9 -1
- package/src/commands/init.js +7 -17
- package/src/commands/lint.js +64 -35
package/.abapGitAgent.example
CHANGED
|
@@ -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
|
@@ -55,6 +55,7 @@ See [Creating New ABAP Projects](docs/install.md#creating-new-abap-projects) to
|
|
|
55
55
|
abapgit-agent init --package ZMY_PACKAGE # Initialize local config
|
|
56
56
|
abapgit-agent create # Create online repo in ABAP
|
|
57
57
|
abapgit-agent import # Import objects from ABAP to git
|
|
58
|
+
abapgit-agent import --branch main # Import to specific branch
|
|
58
59
|
abapgit-agent delete # Delete repo from ABAP
|
|
59
60
|
```
|
|
60
61
|
|
|
@@ -99,17 +100,30 @@ abapgit-agent health # Verify ABAP connection
|
|
|
99
100
|
# Install dependencies
|
|
100
101
|
npm install
|
|
101
102
|
|
|
102
|
-
# Run unit tests (no ABAP system needed)
|
|
103
|
-
npm test
|
|
104
|
-
|
|
105
103
|
# Test a command manually
|
|
106
104
|
node bin/abapgit-agent --help
|
|
107
105
|
node bin/abapgit-agent syntax --files src/zcl_my_class.clas.abap
|
|
106
|
+
```
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
## Running Tests
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Unit tests — no ABAP system needed (fast)
|
|
112
|
+
npm test
|
|
113
|
+
|
|
114
|
+
# Integration tests — requires a configured .abapGitAgent
|
|
115
|
+
npm run test:setup # one-time setup: clone test repos + activate ABAP objects
|
|
116
|
+
npm run test:all # full suite (setup runs automatically on first run)
|
|
117
|
+
|
|
118
|
+
# Run a single suite
|
|
119
|
+
npm run test:cmd # CLI command tests
|
|
120
|
+
npm run test:drop # drop command tests
|
|
121
|
+
npm run test:customize # customize command tests
|
|
122
|
+
npm run test:aunit # ABAP unit tests
|
|
111
123
|
```
|
|
112
124
|
|
|
125
|
+
> **Full integration test guide:** [docs/integration-tests.md](docs/integration-tests.md)
|
|
126
|
+
|
|
113
127
|
## Documentation
|
|
114
128
|
|
|
115
129
|
| Topic | File |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.4",
|
|
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",
|
|
@@ -17,6 +17,31 @@ module.exports = {
|
|
|
17
17
|
async execute(args, context) {
|
|
18
18
|
const { loadConfig, AbapHttp, getTransport, getTransportSettings } = context;
|
|
19
19
|
|
|
20
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
21
|
+
console.log(`
|
|
22
|
+
Usage:
|
|
23
|
+
abapgit-agent customize <table> --set <field=value> [<field=value>...]
|
|
24
|
+
[--transport <TRKORR>] [--no-transport] [--json]
|
|
25
|
+
|
|
26
|
+
Description:
|
|
27
|
+
Write a single row to a SAP customizing table (delivery class C or E).
|
|
28
|
+
The row is identified by the key fields you provide via --set.
|
|
29
|
+
|
|
30
|
+
Parameters:
|
|
31
|
+
<table> Name of the customizing table (e.g. ZTABLE_CONFIG).
|
|
32
|
+
--set <field=value> One or more field=value pairs (space-separated after --set).
|
|
33
|
+
--transport <TRKORR> Record the change in this transport request.
|
|
34
|
+
--no-transport Write without a transport request (local change).
|
|
35
|
+
--json Output result as JSON.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
abapgit-agent customize ZTABLE_CONFIG --set KEY=APP VALUE=active
|
|
39
|
+
abapgit-agent customize ZTABLE_CONFIG --set KEY=APP VALUE=active --transport DEVK900001
|
|
40
|
+
abapgit-agent customize ZTABLE_CONFIG --set KEY=APP VALUE=active --no-transport
|
|
41
|
+
`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
const jsonOutput = args.includes('--json');
|
|
21
46
|
const verbose = args.includes('--verbose');
|
|
22
47
|
const noTransport = args.includes('--no-transport');
|
package/src/commands/import.js
CHANGED
|
@@ -39,11 +39,14 @@ Prerequisites:
|
|
|
39
39
|
- Package must have objects to import
|
|
40
40
|
|
|
41
41
|
Options:
|
|
42
|
+
--branch Branch to push to. Auto-detected from current git branch if omitted.
|
|
42
43
|
--message Commit message (default: "feat: initial import from ABAP package <package>")
|
|
43
44
|
|
|
44
45
|
Examples:
|
|
45
46
|
abapgit-agent import
|
|
46
47
|
abapgit-agent import --message "Initial import from SAP"
|
|
48
|
+
abapgit-agent import --branch main
|
|
49
|
+
abapgit-agent import --branch feature/my-branch --message "Import objects"
|
|
47
50
|
`);
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
@@ -87,6 +90,9 @@ Examples:
|
|
|
87
90
|
commitMessage = args[messageArgIndex + 1];
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
const branchArgIndex = args.indexOf('--branch');
|
|
94
|
+
const branch = branchArgIndex !== -1 ? args[branchArgIndex + 1] : gitUtils.getBranch();
|
|
95
|
+
|
|
90
96
|
if (safeguards.requireImportMessage && !commitMessage) {
|
|
91
97
|
console.error('❌ Error: import requires a commit message for this project\n');
|
|
92
98
|
console.error('Please provide one with:');
|
|
@@ -99,6 +105,7 @@ Examples:
|
|
|
99
105
|
|
|
100
106
|
console.log(`\n📦 Starting import job`);
|
|
101
107
|
console.log(` URL: ${repoUrl}`);
|
|
108
|
+
console.log(` Branch: ${branch}`);
|
|
102
109
|
if (commitMessage) {
|
|
103
110
|
console.log(` Message: ${commitMessage}`);
|
|
104
111
|
}
|
|
@@ -107,7 +114,8 @@ Examples:
|
|
|
107
114
|
const csrfToken = await http.fetchCsrfToken();
|
|
108
115
|
|
|
109
116
|
const data = {
|
|
110
|
-
url: repoUrl
|
|
117
|
+
url: repoUrl,
|
|
118
|
+
branch
|
|
111
119
|
};
|
|
112
120
|
|
|
113
121
|
if (commitMessage) {
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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) {
|
package/src/commands/lint.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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(
|
|
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
|
-
*
|
|
308
|
-
*
|
|
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(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/'/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
|
}
|