abapgit-agent 1.16.1 → 1.16.2
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 +5 -0
- package/package.json +1 -1
- package/src/commands/import.js +13 -1
- package/src/commands/syntax.js +137 -8
- package/src/config.js +2 -0
package/abap/CLAUDE.md
CHANGED
|
@@ -632,6 +632,7 @@ Checked into the repository — applies to all developers. **Read this file at t
|
|
|
632
632
|
|---------|--------|---------|--------|
|
|
633
633
|
| `safeguards.requireFilesForPull` | `true`/`false` | `false` | Requires `--files` on every pull |
|
|
634
634
|
| `safeguards.disablePull` | `true`/`false` | `false` | Disables pull entirely (CI/CD-only projects) |
|
|
635
|
+
| `safeguards.disableImport` | `true`/`false` | `false` | Disables import entirely (one-time or managed operation) |
|
|
635
636
|
| `conflictDetection.mode` | `"abort"`/`"ignore"` | `"abort"` | Whether to abort pull on conflict |
|
|
636
637
|
| `transports.hook.path` | string | `null` | Path to JS module that auto-selects a transport for pull |
|
|
637
638
|
| `transports.hook.description` | string | `null` | Optional label shown when the hook runs |
|
|
@@ -724,6 +725,10 @@ abapgit-agent pull --files src/<name>.clas.abap --sync-xml
|
|
|
724
725
|
1. ✗ Do not run `abapgit-agent run` at all
|
|
725
726
|
2. ✓ Inform the user that run is disabled for this project
|
|
726
727
|
|
|
728
|
+
**When `safeguards.disableImport = true`:**
|
|
729
|
+
1. ✗ Do not run `abapgit-agent import` at all
|
|
730
|
+
2. ✓ Inform the user that import is disabled for this project
|
|
731
|
+
|
|
727
732
|
**When `safeguards.disableProbeClasses = true`:**
|
|
728
733
|
1. ✗ Do not create probe classes in the current project
|
|
729
734
|
2. ✓ If `scratchWorkspace` is configured → create probe class there (see Rule 10)
|
package/package.json
CHANGED
package/src/commands/import.js
CHANGED
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
|
|
19
19
|
async execute(args, context) {
|
|
20
20
|
try {
|
|
21
|
-
const { loadConfig, gitUtils, AbapHttp } = context;
|
|
21
|
+
const { loadConfig, gitUtils, AbapHttp, getSafeguards } = context;
|
|
22
22
|
|
|
23
23
|
// Show help if requested
|
|
24
24
|
const helpIndex = args.findIndex(a => a === '--help' || a === '-h');
|
|
@@ -48,6 +48,18 @@ Examples:
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Get parameters from config
|
|
52
|
+
const safeguards = getSafeguards();
|
|
53
|
+
if (safeguards.disableImport) {
|
|
54
|
+
console.error('❌ Error: import command is disabled for this project\n');
|
|
55
|
+
if (safeguards.reason) {
|
|
56
|
+
console.error(`Reason: ${safeguards.reason}\n`);
|
|
57
|
+
}
|
|
58
|
+
console.error('The import command has been disabled in .abapgit-agent.json');
|
|
59
|
+
console.error('Please contact the project maintainer to enable it.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
// Get parameters from config
|
|
52
64
|
const config = loadConfig();
|
|
53
65
|
const repoUrl = gitUtils.getRemoteUrl();
|
package/src/commands/syntax.js
CHANGED
|
@@ -71,6 +71,7 @@ Examples:
|
|
|
71
71
|
// Group class files together (main + locals)
|
|
72
72
|
const classFilesMap = new Map(); // className -> { main, locals_def, locals_imp }
|
|
73
73
|
const fugrGroupMap = new Map(); // groupName -> { dir, fmFiles: Map<fmName, source> }
|
|
74
|
+
const progIncludeMap = new Map(); // includeName -> { filePath, source }
|
|
74
75
|
const objects = [];
|
|
75
76
|
|
|
76
77
|
for (const file of syntaxFiles) {
|
|
@@ -165,7 +166,7 @@ Examples:
|
|
|
165
166
|
source: source
|
|
166
167
|
};
|
|
167
168
|
|
|
168
|
-
// Read FIXPT from XML metadata for INTF and PROG
|
|
169
|
+
// Read FIXPT and SUBC from XML metadata for INTF and PROG
|
|
169
170
|
if (objType === 'INTF' || objType === 'PROG') {
|
|
170
171
|
const dir = pathModule.dirname(filePath);
|
|
171
172
|
let xmlFile;
|
|
@@ -176,14 +177,22 @@ Examples:
|
|
|
176
177
|
}
|
|
177
178
|
if (xmlFile && fs.existsSync(xmlFile)) {
|
|
178
179
|
const xmlContent = fs.readFileSync(xmlFile, 'utf8');
|
|
179
|
-
// Simple regex to extract FIXPT value
|
|
180
180
|
const fixptMatch = xmlContent.match(/<FIXPT>([^<]+)<\/FIXPT>/);
|
|
181
181
|
if (fixptMatch && fixptMatch[1] === 'X') {
|
|
182
182
|
obj.fixpt = 'X';
|
|
183
183
|
} else {
|
|
184
|
-
// No FIXPT tag means FIXPT=false (blank)
|
|
185
184
|
obj.fixpt = '';
|
|
186
185
|
}
|
|
186
|
+
if (objType === 'PROG') {
|
|
187
|
+
const subcMatch = xmlContent.match(/<SUBC>([^<]+)<\/SUBC>/);
|
|
188
|
+
obj.subc = subcMatch ? subcMatch[1] : '';
|
|
189
|
+
|
|
190
|
+
// INCLUDE programs: stash for parent auto-detection instead of pushing directly
|
|
191
|
+
if (obj.subc === 'I') {
|
|
192
|
+
progIncludeMap.set(objName, { filePath, source });
|
|
193
|
+
continue; // skip objects.push — handled after auto-detection below
|
|
194
|
+
}
|
|
195
|
+
}
|
|
187
196
|
}
|
|
188
197
|
}
|
|
189
198
|
|
|
@@ -352,12 +361,102 @@ Examples:
|
|
|
352
361
|
}
|
|
353
362
|
}
|
|
354
363
|
|
|
364
|
+
// Helper: read SUBC from a .prog.xml file
|
|
365
|
+
function readProgSubc(xmlPath) {
|
|
366
|
+
if (!fs.existsSync(xmlPath)) return '';
|
|
367
|
+
const xml = fs.readFileSync(xmlPath, 'utf8');
|
|
368
|
+
const m = xml.match(/<SUBC>([^<]+)<\/SUBC>/);
|
|
369
|
+
return m ? m[1] : '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Helper: read FIXPT from a .prog.xml file
|
|
373
|
+
function readProgFixpt(xmlPath) {
|
|
374
|
+
if (!fs.existsSync(xmlPath)) return '';
|
|
375
|
+
const xml = fs.readFileSync(xmlPath, 'utf8');
|
|
376
|
+
const m = xml.match(/<FIXPT>([^<]+)<\/FIXPT>/);
|
|
377
|
+
return (m && m[1] === 'X') ? 'X' : '';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Auto-detect parent program for INCLUDE files
|
|
381
|
+
// Scan the same directory for .prog.abap files that contain INCLUDE <name>.
|
|
382
|
+
// Assemble the parent source with the INCLUDE source substituted in-place.
|
|
383
|
+
for (const [includeName, includeData] of progIncludeMap) {
|
|
384
|
+
const includeDir = pathModule.dirname(includeData.filePath);
|
|
385
|
+
let parentFound = false;
|
|
386
|
+
|
|
387
|
+
let dirEntries;
|
|
388
|
+
try {
|
|
389
|
+
dirEntries = fs.readdirSync(includeDir);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
dirEntries = [];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const entry of dirEntries) {
|
|
395
|
+
if (!entry.toLowerCase().endsWith('.prog.abap')) continue;
|
|
396
|
+
const candidatePath = pathModule.join(includeDir, entry);
|
|
397
|
+
if (candidatePath === includeData.filePath) continue; // skip itself
|
|
398
|
+
|
|
399
|
+
// Only consider executable/non-include programs as parents
|
|
400
|
+
const candidateName = entry.split('.')[0].toUpperCase();
|
|
401
|
+
const candidateXml = pathModule.join(includeDir, `${candidateName.toLowerCase()}.prog.xml`);
|
|
402
|
+
const candidateSubc = readProgSubc(candidateXml);
|
|
403
|
+
if (candidateSubc === 'I') continue; // another INCLUDE — skip
|
|
404
|
+
|
|
405
|
+
const parentSource = fs.readFileSync(candidatePath, 'utf8');
|
|
406
|
+
const parentLines = parentSource.split('\n');
|
|
407
|
+
|
|
408
|
+
// Find `INCLUDE <includeName>.` line (case-insensitive)
|
|
409
|
+
const includeRegex = new RegExp(`^\\s*INCLUDE\\s+${includeName}\\s*\\.`, 'i');
|
|
410
|
+
const includeLineIdx = parentLines.findIndex(l => includeRegex.test(l));
|
|
411
|
+
if (includeLineIdx === -1) continue;
|
|
412
|
+
|
|
413
|
+
// Found parent — assemble: replace INCLUDE statement with include's source lines
|
|
414
|
+
const includeLines = includeData.source.split('\n');
|
|
415
|
+
const assembled = [
|
|
416
|
+
...parentLines.slice(0, includeLineIdx),
|
|
417
|
+
...includeLines,
|
|
418
|
+
...parentLines.slice(includeLineIdx + 1)
|
|
419
|
+
];
|
|
420
|
+
const assembledSource = assembled.join('\n');
|
|
421
|
+
|
|
422
|
+
if (!jsonOutput) {
|
|
423
|
+
console.log(` Auto-detected parent: ${entry} (contains INCLUDE ${includeName}.)`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
objects.push({
|
|
427
|
+
type: 'PROG',
|
|
428
|
+
name: candidateName,
|
|
429
|
+
source: assembledSource,
|
|
430
|
+
subc: candidateSubc || '1',
|
|
431
|
+
fixpt: readProgFixpt(candidateXml),
|
|
432
|
+
// offset into assembled source where include lines start (1-based)
|
|
433
|
+
include_offset: includeLineIdx + 1,
|
|
434
|
+
include_line_count: includeLines.length,
|
|
435
|
+
// carry include name for error line mapping
|
|
436
|
+
include_name: includeName
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
parentFound = true;
|
|
440
|
+
break; // use first matching parent
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!parentFound) {
|
|
444
|
+
if (!jsonOutput) {
|
|
445
|
+
console.error(` Warning: No parent program found for INCLUDE ${includeName} — cannot syntax-check standalone`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
355
450
|
if (objects.length === 0) {
|
|
356
451
|
console.error(' No valid files to check');
|
|
357
452
|
process.exit(1);
|
|
358
453
|
}
|
|
359
454
|
const data = {
|
|
360
|
-
objects: objects
|
|
455
|
+
objects: objects.map(o => {
|
|
456
|
+
// Strip client-side INCLUDE tracking fields before sending to ABAP
|
|
457
|
+
const { include_offset, include_line_count, include_name, ...abapObj } = o; // eslint-disable-line no-unused-vars
|
|
458
|
+
return abapObj;
|
|
459
|
+
}),
|
|
361
460
|
uccheck: cloudMode ? '5' : 'X'
|
|
362
461
|
};
|
|
363
462
|
|
|
@@ -387,26 +486,56 @@ Examples:
|
|
|
387
486
|
const fugrFmLabel = (objType === 'FUGR' && sentObj.fugr_include_name)
|
|
388
487
|
? ` (${sentObj.fugr_include_name})` : '';
|
|
389
488
|
|
|
489
|
+
// For INCLUDE-backed checks: determine display label and line remapping
|
|
490
|
+
const includeLabel = sentObj.include_name
|
|
491
|
+
? ` (checking via parent ${objName})` : '';
|
|
492
|
+
|
|
390
493
|
if (objSuccess) {
|
|
391
|
-
|
|
494
|
+
if (sentObj.include_name) {
|
|
495
|
+
console.log(`✅ PROG ${sentObj.include_name} - Syntax check passed${includeLabel}`);
|
|
496
|
+
} else {
|
|
497
|
+
console.log(`✅ ${objType} ${objName}${fugrFmLabel} - Syntax check passed`);
|
|
498
|
+
}
|
|
392
499
|
if (warnings.length > 0) {
|
|
393
500
|
console.log(` (${warnings.length} warning(s))`);
|
|
394
501
|
}
|
|
395
502
|
} else {
|
|
396
|
-
|
|
503
|
+
if (sentObj.include_name) {
|
|
504
|
+
console.log(`❌ PROG ${sentObj.include_name} - Syntax check failed (${errorCount} error(s))${includeLabel}`);
|
|
505
|
+
} else {
|
|
506
|
+
console.log(`❌ ${objType} ${objName}${fugrFmLabel} - Syntax check failed (${errorCount} error(s))`);
|
|
507
|
+
}
|
|
397
508
|
console.log('');
|
|
398
509
|
console.log('Errors:');
|
|
399
510
|
console.log('─'.repeat(60));
|
|
400
511
|
|
|
401
512
|
for (const err of errors) {
|
|
402
|
-
|
|
513
|
+
let line = err.LINE || err.line || '?';
|
|
403
514
|
const column = err.COLUMN || err.column || '';
|
|
404
515
|
const text = err.TEXT || err.text || 'Unknown error';
|
|
405
516
|
const methodName = err.METHOD_NAME || err.method_name || '';
|
|
406
517
|
const include = err.INCLUDE || err.include || '';
|
|
407
518
|
|
|
519
|
+
// Remap line numbers for INCLUDE-backed checks
|
|
520
|
+
let remappedFile = null;
|
|
521
|
+
if (sentObj.include_name && typeof line === 'number') {
|
|
522
|
+
const offset = sentObj.include_offset || 0;
|
|
523
|
+
const count = sentObj.include_line_count || 0;
|
|
524
|
+
if (line >= offset && line < offset + count) {
|
|
525
|
+
// Error is inside the INCLUDE — remap to include-relative line
|
|
526
|
+
line = line - offset + 1;
|
|
527
|
+
remappedFile = `${sentObj.include_name.toLowerCase()}.prog.abap`;
|
|
528
|
+
} else {
|
|
529
|
+
// Error is in the parent program context (e.g. header line)
|
|
530
|
+
remappedFile = `${objName.toLowerCase()}.prog.abap (parent)`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
408
534
|
// Display which file/include the error is in
|
|
409
|
-
if (
|
|
535
|
+
if (remappedFile) {
|
|
536
|
+
// INCLUDE-backed check: show the actual source file the error maps to
|
|
537
|
+
console.log(` In: ${remappedFile}`);
|
|
538
|
+
} else if (include) {
|
|
410
539
|
// For FUGR: include = lowercase FM name → display as '<group>.fugr.<fm_name>.abap'
|
|
411
540
|
if (objType === 'FUGR') {
|
|
412
541
|
const fugrFile = `${objName.toLowerCase()}.fugr.${include}.abap`;
|
package/src/config.js
CHANGED
|
@@ -123,6 +123,7 @@ function getSafeguards() {
|
|
|
123
123
|
requireFilesForPull: projectConfig.safeguards.requireFilesForPull === true,
|
|
124
124
|
disablePull: projectConfig.safeguards.disablePull === true,
|
|
125
125
|
disableRun: projectConfig.safeguards.disableRun === true,
|
|
126
|
+
disableImport: projectConfig.safeguards.disableImport === true,
|
|
126
127
|
disableProbeClasses: projectConfig.safeguards.disableProbeClasses === true,
|
|
127
128
|
reason: projectConfig.safeguards.reason || null
|
|
128
129
|
};
|
|
@@ -133,6 +134,7 @@ function getSafeguards() {
|
|
|
133
134
|
requireFilesForPull: false,
|
|
134
135
|
disablePull: false,
|
|
135
136
|
disableRun: false,
|
|
137
|
+
disableImport: false,
|
|
136
138
|
disableProbeClasses: false,
|
|
137
139
|
reason: null
|
|
138
140
|
};
|