@abapify/adt-atc 0.4.0 → 0.4.1

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.
@@ -0,0 +1,135 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { basename, join, sep } from "node:path";
3
+ //#region \0rolldown/runtime.js
4
+ var __defProp = Object.defineProperty;
5
+ var __exportAll = (all, no_symbols) => {
6
+ let target = {};
7
+ for (var name in all) __defProp(target, name, {
8
+ get: all[name],
9
+ enumerable: true
10
+ });
11
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
12
+ return target;
13
+ };
14
+ //#endregion
15
+ //#region src/resolvers/abapgit.ts
16
+ /**
17
+ * Built-in abapGit Finding Resolver
18
+ *
19
+ * Resolves ATC finding locations to actual git file paths and file-relative
20
+ * line numbers for abapGit repositories (FULL or PREFIX folder logic).
21
+ *
22
+ * This is a built-in resolver that uses only Node.js builtins (fs, path).
23
+ * No external package dependencies required.
24
+ */
25
+ var abapgit_exports = /* @__PURE__ */ __exportAll({ createAbapGitResolver: () => createAbapGitResolver });
26
+ /**
27
+ * Normalise platform-native path separators (\\ on Windows) to POSIX (/).
28
+ * `ResolvedLocation.path` is documented as a git-relative path, and
29
+ * downstream formatters (GitLab JSON, SARIF) require `/` separators.
30
+ */
31
+ function toPosix(p) {
32
+ return sep === "/" ? p : p.split(sep).join("/");
33
+ }
34
+ const fileCache = /* @__PURE__ */ new Map();
35
+ function collectSourceFiles(root) {
36
+ const result = [];
37
+ const stack = [root];
38
+ while (stack.length > 0) {
39
+ const current = stack.pop();
40
+ let entries;
41
+ try {
42
+ entries = readdirSync(current, { withFileTypes: true });
43
+ } catch {
44
+ continue;
45
+ }
46
+ for (const entry of entries) {
47
+ const fullPath = join(current, entry.name);
48
+ if (entry.isDirectory()) stack.push(fullPath);
49
+ else if (entry.isFile() && (fullPath.endsWith(".abap") || fullPath.endsWith(".xml"))) result.push(fullPath);
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ function getFileLines(filePath) {
55
+ if (fileCache.has(filePath)) return fileCache.get(filePath);
56
+ try {
57
+ const lines = readFileSync(filePath, "utf8").split("\n");
58
+ fileCache.set(filePath, lines);
59
+ return lines;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ function parseMethodRanges(lines) {
65
+ const ranges = [];
66
+ let currentMethod = null;
67
+ let methodStart = 0;
68
+ for (let i = 0; i < lines.length; i++) {
69
+ const match = lines[i].match(/^\s*METHOD\s+(\w+)/i);
70
+ if (match) {
71
+ currentMethod = match[1].toLowerCase();
72
+ methodStart = i + 1;
73
+ }
74
+ if (currentMethod && /^\s*ENDMETHOD/i.test(lines[i])) {
75
+ const endLine = i + 1;
76
+ ranges.push({
77
+ name: currentMethod,
78
+ startLine: methodStart,
79
+ length: endLine - methodStart + 1
80
+ });
81
+ currentMethod = null;
82
+ }
83
+ }
84
+ return ranges;
85
+ }
86
+ function convertLine(atcLine, methodName, filePath) {
87
+ if (!filePath.endsWith(".clas.abap")) return atcLine;
88
+ const lines = getFileLines(filePath);
89
+ if (!lines) return atcLine;
90
+ const ranges = parseMethodRanges(lines);
91
+ if (ranges.length === 0) return atcLine;
92
+ if (methodName) {
93
+ const method = ranges.find((r) => r.name === methodName.toLowerCase());
94
+ if (method) return method.startLine + atcLine - 1;
95
+ }
96
+ if (ranges.length === 1) return ranges[0].startLine + atcLine - 1;
97
+ const candidates = ranges.filter((r) => atcLine <= r.length).sort((a, b) => a.length - b.length);
98
+ if (candidates.length > 0) return candidates[0].startLine + atcLine - 1;
99
+ return atcLine;
100
+ }
101
+ /**
102
+ * Create a built-in abapGit finding resolver.
103
+ *
104
+ * Scans src/ to build a filename → git-path lookup, then resolves
105
+ * ATC object references to actual file paths and converts method-relative
106
+ * line numbers to file-relative.
107
+ *
108
+ * @param srcRoot - Path to scan for source files (default: 'src/')
109
+ */
110
+ function createAbapGitResolver(srcRoot = "src/") {
111
+ const lookup = /* @__PURE__ */ new Map();
112
+ try {
113
+ if (existsSync(srcRoot)) {
114
+ const files = collectSourceFiles(srcRoot).sort((a, b) => a.localeCompare(b));
115
+ for (const f of files) {
116
+ const name = basename(f);
117
+ if (!lookup.has(name)) lookup.set(name, toPosix(f));
118
+ }
119
+ }
120
+ } catch {}
121
+ if (lookup.size > 0) console.log(`📂 Finding resolver: ${lookup.size} files indexed from ${srcRoot}`);
122
+ return { async resolve(objectType, objectName, atcLine, methodName) {
123
+ const expectedFilename = `${objectName.toLowerCase()}.${objectType.toLowerCase()}.abap`;
124
+ const resolvedPath = lookup.get(expectedFilename);
125
+ if (!resolvedPath) return null;
126
+ return {
127
+ path: resolvedPath,
128
+ line: convertLine(atcLine, methodName, resolvedPath)
129
+ };
130
+ } };
131
+ }
132
+ //#endregion
133
+ export { createAbapGitResolver as n, abapgit_exports as t };
134
+
135
+ //# sourceMappingURL=abapgit-DfddrbJx.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abapgit-DfddrbJx.mjs","names":[],"sources":["../src/resolvers/abapgit.ts"],"sourcesContent":["/**\n * Built-in abapGit Finding Resolver\n *\n * Resolves ATC finding locations to actual git file paths and file-relative\n * line numbers for abapGit repositories (FULL or PREFIX folder logic).\n *\n * This is a built-in resolver that uses only Node.js builtins (fs, path).\n * No external package dependencies required.\n */\n\nimport { readFileSync, existsSync, readdirSync, type Dirent } from 'node:fs';\nimport { basename, join, sep } from 'node:path';\nimport type { FindingResolver, ResolvedLocation } from '../types';\n\n/**\n * Normalise platform-native path separators (\\\\ on Windows) to POSIX (/).\n * `ResolvedLocation.path` is documented as a git-relative path, and\n * downstream formatters (GitLab JSON, SARIF) require `/` separators.\n */\nfunction toPosix(p: string): string {\n return sep === '/' ? p : p.split(sep).join('/');\n}\n\n// ── Method range parsing ────────────────────────────────────────────────\n\ninterface MethodRange {\n name: string;\n startLine: number;\n length: number;\n}\n\nconst fileCache = new Map<string, string[]>();\n\nfunction collectSourceFiles(root: string): string[] {\n const result: string[] = [];\n const stack: string[] = [root];\n\n while (stack.length > 0) {\n const current = stack.pop()!;\n let entries: Dirent[];\n try {\n entries = readdirSync(current, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n const fullPath = join(current, entry.name);\n if (entry.isDirectory()) {\n stack.push(fullPath);\n } else if (\n entry.isFile() &&\n (fullPath.endsWith('.abap') || fullPath.endsWith('.xml'))\n ) {\n result.push(fullPath);\n }\n }\n }\n\n return result;\n}\n\nfunction getFileLines(filePath: string): string[] | null {\n if (fileCache.has(filePath)) return fileCache.get(filePath)!;\n try {\n const content = readFileSync(filePath, 'utf8');\n const lines = content.split('\\n');\n fileCache.set(filePath, lines);\n return lines;\n } catch {\n return null;\n }\n}\n\nfunction parseMethodRanges(lines: string[]): MethodRange[] {\n const ranges: MethodRange[] = [];\n let currentMethod: string | null = null;\n let methodStart = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const match = lines[i].match(/^\\s*METHOD\\s+(\\w+)/i);\n if (match) {\n currentMethod = match[1].toLowerCase();\n methodStart = i + 1; // 1-based\n }\n if (currentMethod && /^\\s*ENDMETHOD/i.test(lines[i])) {\n const endLine = i + 1;\n ranges.push({\n name: currentMethod,\n startLine: methodStart,\n length: endLine - methodStart + 1,\n });\n currentMethod = null;\n }\n }\n return ranges;\n}\n\nfunction convertLine(\n atcLine: number,\n methodName: string | undefined,\n filePath: string,\n): number {\n if (!filePath.endsWith('.clas.abap')) return atcLine;\n\n const lines = getFileLines(filePath);\n if (!lines) return atcLine;\n\n const ranges = parseMethodRanges(lines);\n if (ranges.length === 0) return atcLine;\n\n // Best case: method name known from ATC location URI\n if (methodName) {\n const method = ranges.find((r) => r.name === methodName.toLowerCase());\n if (method) return method.startLine + atcLine - 1;\n }\n\n // Single method: use it\n if (ranges.length === 1) return ranges[0].startLine + atcLine - 1;\n\n // Heuristic: smallest method where atcLine fits\n const candidates = ranges\n .filter((r) => atcLine <= r.length)\n .sort((a, b) => a.length - b.length);\n if (candidates.length > 0) return candidates[0].startLine + atcLine - 1;\n\n return atcLine;\n}\n\n// ── Resolver factory ────────────────────────────────────────────────────\n\n/**\n * Create a built-in abapGit finding resolver.\n *\n * Scans src/ to build a filename → git-path lookup, then resolves\n * ATC object references to actual file paths and converts method-relative\n * line numbers to file-relative.\n *\n * @param srcRoot - Path to scan for source files (default: 'src/')\n */\nexport function createAbapGitResolver(srcRoot = 'src/'): FindingResolver {\n const lookup = new Map<string, string>();\n\n try {\n if (existsSync(srcRoot)) {\n const files = collectSourceFiles(srcRoot).sort((a, b) =>\n a.localeCompare(b),\n );\n\n for (const f of files) {\n const name = basename(f);\n if (!lookup.has(name)) {\n // Store POSIX-normalised paths: consumers treat\n // ResolvedLocation.path as a git-relative path (uses `/`).\n lookup.set(name, toPosix(f));\n }\n }\n }\n } catch {\n // src/ scan failed — resolver will return null for all findings\n }\n\n if (lookup.size > 0) {\n console.log(\n `📂 Finding resolver: ${lookup.size} files indexed from ${srcRoot}`,\n );\n }\n\n return {\n async resolve(\n objectType: string,\n objectName: string,\n atcLine: number,\n methodName?: string,\n ): Promise<ResolvedLocation | null> {\n const expectedFilename = `${objectName.toLowerCase()}.${objectType.toLowerCase()}.abap`;\n const resolvedPath = lookup.get(expectedFilename);\n\n if (!resolvedPath) return null;\n\n const fileLine = convertLine(atcLine, methodName, resolvedPath);\n return { path: resolvedPath, line: fileLine };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,385 @@
1
+ import chalk from "chalk";
2
+ import { writeFile } from "fs/promises";
3
+ //#region src/formatters/sarif.ts
4
+ /**
5
+ * SARIF Formatter
6
+ *
7
+ * Outputs ATC findings in SARIF format for GitHub Code Scanning.
8
+ */
9
+ async function outputSarifReport(result, outputFile, _targetName) {
10
+ const rules = Array.from(new Map(result.findings.map((f) => [f.checkId, {
11
+ id: f.checkId,
12
+ name: f.checkTitle || f.checkId,
13
+ shortDescription: { text: f.checkTitle || f.checkId },
14
+ fullDescription: { text: f.checkTitle || f.checkId },
15
+ defaultConfiguration: { level: f.priority === 1 ? "error" : f.priority === 2 ? "warning" : "note" },
16
+ properties: {
17
+ tags: ["abap", "code-quality"],
18
+ precision: "high"
19
+ }
20
+ }])).values());
21
+ const results = result.findings.map((finding) => {
22
+ const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;
23
+ const lineMatch = finding.location?.match(/start=(\d+)/);
24
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : 1;
25
+ return {
26
+ ruleId: finding.checkId,
27
+ level: finding.priority === 1 ? "error" : finding.priority === 2 ? "warning" : "note",
28
+ message: { text: finding.messageText },
29
+ locations: [{ physicalLocation: {
30
+ artifactLocation: { uri: filePath },
31
+ region: {
32
+ startLine: line,
33
+ startColumn: 1
34
+ }
35
+ } }],
36
+ partialFingerprints: { primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}` }
37
+ };
38
+ });
39
+ const sarifReport = {
40
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
41
+ version: "2.1.0",
42
+ runs: [{
43
+ tool: { driver: {
44
+ name: "ABAP Test Cockpit",
45
+ version: "1.0.0",
46
+ informationUri: "https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html",
47
+ rules: Array.from(rules)
48
+ } },
49
+ results
50
+ }]
51
+ };
52
+ await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));
53
+ console.log(`\n📄 SARIF report written to: ${outputFile}`);
54
+ console.log(`📊 ${result.totalFindings} issues exported for GitHub Code Scanning`);
55
+ }
56
+ //#endregion
57
+ //#region src/formatters/gitlab.ts
58
+ /**
59
+ * GitLab Code Quality Formatter
60
+ *
61
+ * Outputs ATC findings in GitLab Code Quality format.
62
+ *
63
+ * This formatter is a pure output formatter — it maps ATC findings to the
64
+ * GitLab Code Quality JSON structure. Path resolution and line number
65
+ * conversion are delegated to an optional FindingResolver.
66
+ *
67
+ * Without a resolver, the formatter produces PREFIX-style paths and raw
68
+ * ATC (method-relative) line numbers. With a resolver (e.g., from
69
+ * @abapify/adt-plugin-abapgit), it produces correct git paths and
70
+ * file-relative line numbers.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Without resolver (standalone CLI usage)
75
+ * await outputGitLabCodeQuality(result, 'report.json');
76
+ *
77
+ * // With resolver (inside an abapgit repo)
78
+ * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';
79
+ * const resolver = await createFindingResolver();
80
+ * await outputGitLabCodeQuality(result, 'report.json', { resolver });
81
+ * ```
82
+ */
83
+ function extractMethodName(location) {
84
+ if (!location) return void 0;
85
+ const match = location.match(/\/methods\/(\w+)/i) || location.match(/[;?&]name=(\w+)/i);
86
+ return match ? match[1].toLowerCase() : void 0;
87
+ }
88
+ function prefixPath(finding) {
89
+ return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;
90
+ }
91
+ async function outputGitLabCodeQuality(result, outputFile, options) {
92
+ const resolver = options?.resolver;
93
+ const gitlabReport = await Promise.all(result.findings.map(async (finding) => {
94
+ let severity;
95
+ switch (finding.priority) {
96
+ case 1:
97
+ severity = "blocker";
98
+ break;
99
+ case 2:
100
+ severity = "major";
101
+ break;
102
+ case 3:
103
+ severity = "minor";
104
+ break;
105
+ default:
106
+ severity = "info";
107
+ break;
108
+ }
109
+ const lineMatch = finding.location?.match(/start=(\d+)/);
110
+ const atcLine = lineMatch ? parseInt(lineMatch[1], 10) : 1;
111
+ const methodName = extractMethodName(finding.location);
112
+ let filePath;
113
+ let fileLine;
114
+ if (resolver) {
115
+ const resolved = await resolver.resolve(finding.objectType, finding.objectName, atcLine, methodName);
116
+ if (resolved) {
117
+ filePath = resolved.path;
118
+ fileLine = resolved.line;
119
+ } else {
120
+ filePath = prefixPath(finding);
121
+ fileLine = atcLine;
122
+ }
123
+ } else {
124
+ filePath = prefixPath(finding);
125
+ fileLine = atcLine;
126
+ }
127
+ const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;
128
+ return {
129
+ description: finding.messageText,
130
+ check_name: finding.checkTitle || finding.checkId,
131
+ fingerprint,
132
+ severity,
133
+ location: {
134
+ path: filePath,
135
+ lines: {
136
+ begin: fileLine,
137
+ end: fileLine
138
+ }
139
+ },
140
+ ...methodName && { method: methodName },
141
+ ...finding.location && { atc_location: finding.location }
142
+ };
143
+ }));
144
+ await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));
145
+ console.log(`\n📄 GitLab Code Quality report written to: ${outputFile}`);
146
+ console.log(`📊 ${result.totalFindings} issues exported`);
147
+ }
148
+ //#endregion
149
+ //#region src/commands/atc.ts
150
+ /**
151
+ * Create OSC 8 hyperlink for terminal
152
+ */
153
+ function hyperlink(text, url) {
154
+ const OSC = "\x1B]";
155
+ const BEL = "\x07";
156
+ const SEP = ";";
157
+ return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;
158
+ }
159
+ /**
160
+ * Create ADT Eclipse link
161
+ */
162
+ function adtLink(name, uri, systemName) {
163
+ if (!systemName || !name || !uri) return name ? chalk.cyan(name) : "";
164
+ const url = `adt://${systemName}${uri.startsWith("/sap/bc/adt") ? uri : `/sap/bc/adt${uri}`}`;
165
+ return hyperlink(chalk.cyan(name), url);
166
+ }
167
+ /**
168
+ * Convert worklist response to AtcResult
169
+ */
170
+ function convertWorklistToResult(worklistResponse, checkVariant) {
171
+ const findings = [];
172
+ if (worklistResponse.worklist) {
173
+ const objects = worklistResponse.worklist.objects.object || [];
174
+ for (const obj of objects) {
175
+ const objFindings = obj.findings.finding || [];
176
+ for (const finding of objFindings) findings.push({
177
+ checkId: finding.checkId || "",
178
+ checkTitle: finding.checkTitle || "",
179
+ messageId: finding.messageId || "",
180
+ priority: parseInt(finding.priority || "3", 10),
181
+ messageText: finding.messageTitle || "",
182
+ objectUri: obj.uri || "",
183
+ objectType: obj.type || "",
184
+ objectName: obj.name || "",
185
+ location: finding.location,
186
+ findingUri: finding.uri
187
+ });
188
+ }
189
+ }
190
+ const errorCount = findings.filter((f) => f.priority === 1).length;
191
+ const warningCount = findings.filter((f) => f.priority === 2).length;
192
+ const infoCount = findings.filter((f) => f.priority >= 3).length;
193
+ return {
194
+ checkVariant,
195
+ totalFindings: findings.length,
196
+ errorCount,
197
+ warningCount,
198
+ infoCount,
199
+ findings
200
+ };
201
+ }
202
+ /**
203
+ * Display ATC results in console
204
+ */
205
+ function displayAtcResults(result, systemName) {
206
+ if (result.totalFindings === 0) {
207
+ console.log(`\n✅ ATC check passed - No issues found!`);
208
+ console.log(` 🎯 Variant: ${result.checkVariant}`);
209
+ return;
210
+ }
211
+ console.log(`\n📊 ATC Results Summary:`);
212
+ console.log(` 🎯 Variant: ${result.checkVariant}`);
213
+ if (result.errorCount > 0) console.log(` ❌ Errors: ${result.errorCount}`);
214
+ if (result.warningCount > 0) console.log(` ⚠️ Warnings: ${result.warningCount}`);
215
+ if (result.infoCount > 0) console.log(` ℹ️ Info: ${result.infoCount}`);
216
+ console.log(` 📋 Total Issues: ${result.totalFindings}`);
217
+ if (result.findings && result.findings.length > 0) {
218
+ console.log(`\n📄 Findings:`);
219
+ result.findings.slice(0, 5).forEach((finding) => {
220
+ const priorityIcon = finding.priority === 1 ? "❌" : finding.priority === 2 ? "⚠️" : "ℹ️";
221
+ const objectLink = finding.location ? adtLink(finding.objectName, finding.location, systemName) : finding.objectName;
222
+ console.log(` ${priorityIcon} ${finding.checkTitle || finding.checkId}`);
223
+ console.log(` ${finding.messageText}`);
224
+ console.log(` Object: ${objectLink} (${finding.objectType})`);
225
+ });
226
+ if (result.findings.length > 5) console.log(` ... (${result.findings.length - 5} more findings)`);
227
+ }
228
+ console.log(`\n💡 Use --debug for detailed results`);
229
+ }
230
+ /**
231
+ * ATC Command Plugin
232
+ */
233
+ const atcCommand = {
234
+ name: "atc",
235
+ description: "Run ABAP Test Cockpit (ATC) checks",
236
+ options: [
237
+ {
238
+ flags: "-p, --package <package>",
239
+ description: "Run ATC on package"
240
+ },
241
+ {
242
+ flags: "-o, --object <uri>",
243
+ description: "Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)"
244
+ },
245
+ {
246
+ flags: "-t, --transport <transport>",
247
+ description: "Run ATC on transport request (e.g., S0DK942970)"
248
+ },
249
+ {
250
+ flags: "-f, --from-file <file>",
251
+ description: "Run ATC on objects listed in file (one URI per line)"
252
+ },
253
+ {
254
+ flags: "--variant <variant>",
255
+ description: "ATC check variant (default: from system customizing)"
256
+ },
257
+ {
258
+ flags: "--max-results <number>",
259
+ description: "Maximum number of results",
260
+ default: "100"
261
+ },
262
+ {
263
+ flags: "--format <format>",
264
+ description: "Output format: console, json, gitlab, sarif",
265
+ default: "console"
266
+ },
267
+ {
268
+ flags: "--output <file>",
269
+ description: "Output file (required for gitlab/sarif format)"
270
+ },
271
+ {
272
+ flags: "--resolver <name>",
273
+ description: "Finding resolver plugin for path/line resolution (e.g., abapgit)"
274
+ }
275
+ ],
276
+ async execute(args, ctx) {
277
+ const options = args;
278
+ const targetCount = [
279
+ options.package,
280
+ options.object,
281
+ options.transport,
282
+ options.fromFile
283
+ ].filter(Boolean).length;
284
+ if (targetCount === 0) {
285
+ ctx.logger.error("❌ One of --package, --object, --transport, or --from-file is required");
286
+ process.exit(1);
287
+ }
288
+ if (targetCount > 1) {
289
+ ctx.logger.error("❌ Only one of --package, --object, --transport, or --from-file can be specified");
290
+ process.exit(1);
291
+ }
292
+ if ((options.format === "gitlab" || options.format === "sarif") && !options.output) {
293
+ ctx.logger.error(`❌ --output is required for ${options.format} format`);
294
+ process.exit(1);
295
+ }
296
+ if (!ctx.getAdtClient) {
297
+ ctx.logger.error("❌ ADT client not available. This command requires authentication.");
298
+ ctx.logger.error(" Run: adt auth login");
299
+ process.exit(1);
300
+ }
301
+ const client = await ctx.getAdtClient();
302
+ ctx.logger.info("🔍 Running ABAP Test Cockpit checks...");
303
+ let targetUris;
304
+ let targetName;
305
+ if (options.fromFile) {
306
+ const { readFileSync } = await import("fs");
307
+ targetUris = readFileSync(options.fromFile, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
308
+ if (targetUris.length === 0) {
309
+ ctx.logger.error(`❌ No objects found in ${options.fromFile}`);
310
+ process.exit(1);
311
+ }
312
+ targetName = `${targetUris.length} objects from ${options.fromFile}`;
313
+ ctx.logger.info(`📄 Target: ${targetName}`);
314
+ } else if (options.transport) {
315
+ targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];
316
+ targetName = `Transport ${options.transport}`;
317
+ ctx.logger.info(`🚚 Target: ${targetName}`);
318
+ } else if (options.package) {
319
+ targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];
320
+ targetName = `Package ${options.package.toUpperCase()}`;
321
+ ctx.logger.info(`📦 Target: ${targetName}`);
322
+ } else {
323
+ targetUris = [options.object];
324
+ targetName = options.object;
325
+ ctx.logger.info(`📄 Target: ${targetName}`);
326
+ }
327
+ ctx.logger.info("📋 Reading ATC customizing...");
328
+ const customizing = await client.adt.atc.customizing.get();
329
+ let checkVariant = options.variant;
330
+ if (!checkVariant) {
331
+ const variantProp = customizing.customizing.properties.property?.find((p) => p.name === "systemCheckVariant");
332
+ if (variantProp?.value) checkVariant = variantProp.value;
333
+ else checkVariant = "DEFAULT";
334
+ }
335
+ ctx.logger.info(`🎯 Check variant: ${checkVariant}`);
336
+ ctx.logger.info("📋 Creating ATC worklist...");
337
+ const worklistCreateResponse = await client.adt.atc.worklists.create({ checkVariant });
338
+ let worklistId;
339
+ if (typeof worklistCreateResponse === "string") {
340
+ const idMatch = worklistCreateResponse.match(/id="([^"]+)"/);
341
+ worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();
342
+ } else if ("worklist" in worklistCreateResponse && worklistCreateResponse.worklist?.id) worklistId = worklistCreateResponse.worklist.id;
343
+ else if ("worklistRun" in worklistCreateResponse && worklistCreateResponse.worklistRun?.worklistId) worklistId = worklistCreateResponse.worklistRun.worklistId;
344
+ else {
345
+ ctx.logger.error("❌ Failed to get worklist ID from response");
346
+ process.exit(1);
347
+ }
348
+ ctx.logger.info(`📋 Worklist created: ${worklistId}`);
349
+ ctx.logger.info("⏳ Running ATC analysis...");
350
+ const runData = { run: {
351
+ maximumVerdicts: parseInt(options.maxResults || "100", 10),
352
+ objectSets: { objectSet: [{
353
+ kind: "inclusive",
354
+ objectReferences: { objectReference: targetUris.map((uri) => ({ uri })) }
355
+ }] }
356
+ } };
357
+ await client.adt.atc.runs.post({ worklistId }, runData);
358
+ ctx.logger.info("📊 Fetching results...");
359
+ const result = convertWorklistToResult(await client.adt.atc.worklists.get(worklistId, { includeExemptedFindings: "false" }), checkVariant);
360
+ let resolver;
361
+ if (options.resolver) {
362
+ const name = options.resolver;
363
+ if (name === "abapgit") {
364
+ const { createAbapGitResolver } = await import("./abapgit-DfddrbJx.mjs").then((n) => n.t);
365
+ resolver = createAbapGitResolver();
366
+ } else try {
367
+ ctx.logger.info(`📂 Loading external resolver: ${name}`);
368
+ const mod = await import(name);
369
+ if (typeof mod.createFindingResolver === "function") resolver = mod.createFindingResolver();
370
+ else ctx.logger.warn(`⚠️ Module ${name} does not export createFindingResolver()`);
371
+ } catch (e) {
372
+ ctx.logger.warn(`⚠️ Failed to load resolver "${name}": ${e instanceof Error ? e.message : e}`);
373
+ }
374
+ }
375
+ if (options.format === "json") console.log(JSON.stringify(result, null, 2));
376
+ else if (options.format === "gitlab" && options.output) await outputGitLabCodeQuality(result, options.output, { resolver });
377
+ else if (options.format === "sarif" && options.output) await outputSarifReport(result, options.output, targetName);
378
+ else displayAtcResults(result, ctx.adtSystemName);
379
+ if (result.errorCount > 0) process.exit(1);
380
+ }
381
+ };
382
+ //#endregion
383
+ export { outputGitLabCodeQuality as n, outputSarifReport as r, atcCommand as t };
384
+
385
+ //# sourceMappingURL=atc-DWwSD88H.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atc-DWwSD88H.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/commands/atc.ts"],"sourcesContent":["/**\n * SARIF Formatter\n *\n * Outputs ATC findings in SARIF format for GitHub Code Scanning.\n */\n\nimport { writeFile } from 'fs/promises';\nimport type { AtcResult, AtcFinding } from '../types';\n\nexport async function outputSarifReport(\n result: AtcResult,\n outputFile: string,\n _targetName?: string,\n): Promise<void> {\n // Transform ATC findings to SARIF format\n const rules = Array.from(\n new Map(\n result.findings.map((f) => [\n f.checkId,\n {\n id: f.checkId,\n name: f.checkTitle || f.checkId,\n shortDescription: { text: f.checkTitle || f.checkId },\n fullDescription: { text: f.checkTitle || f.checkId },\n defaultConfiguration: {\n level:\n f.priority === 1\n ? 'error'\n : f.priority === 2\n ? 'warning'\n : 'note',\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n },\n },\n ]),\n ).values(),\n );\n\n const results = result.findings.map((finding: AtcFinding) => {\n const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;\n\n // Parse line number from location if available\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const line = lineMatch ? parseInt(lineMatch[1], 10) : 1;\n\n return {\n ruleId: finding.checkId,\n level:\n finding.priority === 1\n ? 'error'\n : finding.priority === 2\n ? 'warning'\n : 'note',\n message: { text: finding.messageText },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: filePath },\n region: {\n startLine: line,\n startColumn: 1,\n },\n },\n },\n ],\n partialFingerprints: {\n primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}`,\n },\n };\n });\n\n const sarifReport = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'ABAP Test Cockpit',\n version: '1.0.0',\n informationUri:\n 'https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html',\n rules: Array.from(rules),\n },\n },\n results,\n },\n ],\n };\n\n await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));\n console.log(`\\n📄 SARIF report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalFindings} issues exported for GitHub Code Scanning`,\n );\n}\n","/**\n * GitLab Code Quality Formatter\n *\n * Outputs ATC findings in GitLab Code Quality format.\n *\n * This formatter is a pure output formatter — it maps ATC findings to the\n * GitLab Code Quality JSON structure. Path resolution and line number\n * conversion are delegated to an optional FindingResolver.\n *\n * Without a resolver, the formatter produces PREFIX-style paths and raw\n * ATC (method-relative) line numbers. With a resolver (e.g., from\n * @abapify/adt-plugin-abapgit), it produces correct git paths and\n * file-relative line numbers.\n *\n * @example\n * ```typescript\n * // Without resolver (standalone CLI usage)\n * await outputGitLabCodeQuality(result, 'report.json');\n *\n * // With resolver (inside an abapgit repo)\n * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';\n * const resolver = await createFindingResolver();\n * await outputGitLabCodeQuality(result, 'report.json', { resolver });\n * ```\n */\n\nimport { writeFile } from 'fs/promises';\nimport type { AtcResult, AtcFinding, FindingResolver } from '../types';\n\n/**\n * Options for the GitLab Code Quality formatter\n */\nexport interface GitLabFormatterOptions {\n /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */\n resolver?: FindingResolver;\n}\n\n// ── Extract method name from ATC location URI ───────────────────────────\n// Format 1: /sap/bc/adt/oo/classes/zcl_foo/methods/my_method#start=21,0\n// Format 2: ...#type=CLAS%2FOM;name=MY_METHOD;start=21\n\nfunction extractMethodName(location?: string): string | undefined {\n if (!location) return undefined;\n const match =\n location.match(/\\/methods\\/(\\w+)/i) || location.match(/[;?&]name=(\\w+)/i);\n return match ? match[1].toLowerCase() : undefined;\n}\n\n// ── Construct PREFIX-style fallback path ─────────────────────────────────\n\nfunction prefixPath(finding: AtcFinding): string {\n return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;\n}\n\n// ── Main formatter ──────────────────────────────────────────────────────\n\nexport async function outputGitLabCodeQuality(\n result: AtcResult,\n outputFile: string,\n options?: GitLabFormatterOptions,\n): Promise<void> {\n const resolver = options?.resolver;\n\n // Transform ATC findings to GitLab Code Quality format\n const gitlabReport = await Promise.all(\n result.findings.map(async (finding: AtcFinding) => {\n // Map ATC priority to GitLab severity\n let severity: string;\n switch (finding.priority) {\n case 1:\n severity = 'blocker';\n break;\n case 2:\n severity = 'major';\n break;\n case 3:\n severity = 'minor';\n break;\n default:\n severity = 'info';\n break;\n }\n\n // Parse ATC line number from location\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const atcLine = lineMatch ? parseInt(lineMatch[1], 10) : 1;\n\n // Extract method name from ATC location URI\n const methodName = extractMethodName(finding.location);\n\n // Resolve path and line via plugin, or fall back to PREFIX-style\n let filePath: string;\n let fileLine: number;\n\n if (resolver) {\n const resolved = await resolver.resolve(\n finding.objectType,\n finding.objectName,\n atcLine,\n methodName,\n );\n if (resolved) {\n filePath = resolved.path;\n fileLine = resolved.line;\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n\n // Create unique fingerprint\n const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;\n\n return {\n description: finding.messageText,\n check_name: finding.checkTitle || finding.checkId,\n fingerprint,\n severity,\n location: {\n path: filePath,\n lines: {\n begin: fileLine,\n end: fileLine,\n },\n },\n // Extra fields for downstream processing\n ...(methodName && { method: methodName }),\n ...(finding.location && { atc_location: finding.location }),\n };\n }),\n );\n\n await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));\n console.log(`\\n📄 GitLab Code Quality report written to: ${outputFile}`);\n console.log(`📊 ${result.totalFindings} issues exported`);\n}\n","/**\n * ATC Command Plugin\n *\n * CLI-agnostic command for running ABAP Test Cockpit checks.\n * Uses the CliContext.getAdtClient() factory for API access.\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport chalk from 'chalk';\nimport { outputSarifReport, outputGitLabCodeQuality } from '../formatters';\nimport type { AtcResult, AtcFinding, OutputFormat } from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.atc namespace provides all ATC operations\ninterface AdtClient {\n adt: {\n atc: {\n customizing: {\n get: () => Promise<{\n customizing: {\n properties: { property?: Array<{ name: string; value?: string }> };\n };\n }>;\n };\n worklists: {\n create: (params: {\n checkVariant: string;\n }) => Promise<\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } }\n >;\n get: (\n id: string,\n params: { includeExemptedFindings: string },\n ) => Promise<WorklistResponse>;\n };\n runs: {\n post: (\n params: { worklistId: string },\n body: {\n run: {\n maximumVerdicts: number;\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n },\n ) => Promise<void>;\n };\n };\n };\n}\n\n// Worklist response type\ninterface WorklistResponse {\n worklist?: {\n id: string;\n timestamp: string;\n objects: {\n object?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n findings: {\n finding?: Array<{\n uri?: string;\n location?: string;\n priority?: string;\n checkId?: string;\n checkTitle?: string;\n messageId?: string;\n messageTitle?: string;\n }>;\n };\n }>;\n };\n };\n worklistRun?: { worklistId: string };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? chalk.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(chalk.cyan(name), url);\n}\n\n/**\n * Convert worklist response to AtcResult\n */\nfunction convertWorklistToResult(\n worklistResponse: WorklistResponse,\n checkVariant: string,\n): AtcResult {\n const findings: AtcFinding[] = [];\n\n if (worklistResponse.worklist) {\n const objects = worklistResponse.worklist.objects.object || [];\n\n for (const obj of objects) {\n const objFindings = obj.findings.finding || [];\n for (const finding of objFindings) {\n findings.push({\n checkId: finding.checkId || '',\n checkTitle: finding.checkTitle || '',\n messageId: finding.messageId || '',\n priority: parseInt(finding.priority || '3', 10),\n messageText: finding.messageTitle || '',\n objectUri: obj.uri || '',\n objectType: obj.type || '',\n objectName: obj.name || '',\n location: finding.location,\n findingUri: finding.uri,\n });\n }\n }\n }\n\n const errorCount = findings.filter((f) => f.priority === 1).length;\n const warningCount = findings.filter((f) => f.priority === 2).length;\n const infoCount = findings.filter((f) => f.priority >= 3).length;\n\n return {\n checkVariant,\n totalFindings: findings.length,\n errorCount,\n warningCount,\n infoCount,\n findings,\n };\n}\n\n/**\n * Display ATC results in console\n */\nfunction displayAtcResults(result: AtcResult, systemName?: string): void {\n if (result.totalFindings === 0) {\n console.log(`\\n✅ ATC check passed - No issues found!`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n return;\n }\n\n console.log(`\\n📊 ATC Results Summary:`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n if (result.errorCount > 0) console.log(` ❌ Errors: ${result.errorCount}`);\n if (result.warningCount > 0)\n console.log(` ⚠️ Warnings: ${result.warningCount}`);\n if (result.infoCount > 0) console.log(` ℹ️ Info: ${result.infoCount}`);\n console.log(` 📋 Total Issues: ${result.totalFindings}`);\n\n if (result.findings && result.findings.length > 0) {\n console.log(`\\n📄 Findings:`);\n result.findings.slice(0, 5).forEach((finding) => {\n const priorityIcon =\n finding.priority === 1 ? '❌' : finding.priority === 2 ? '⚠️' : 'ℹ️';\n const objectLink = finding.location\n ? adtLink(finding.objectName, finding.location, systemName)\n : finding.objectName;\n console.log(\n ` ${priorityIcon} ${finding.checkTitle || finding.checkId}`,\n );\n console.log(` ${finding.messageText}`);\n console.log(` Object: ${objectLink} (${finding.objectType})`);\n });\n\n if (result.findings.length > 5) {\n console.log(` ... (${result.findings.length - 5} more findings)`);\n }\n }\n\n console.log(`\\n💡 Use --debug for detailed results`);\n}\n\n/**\n * ATC Command Plugin\n */\nexport const atcCommand: CliCommandPlugin = {\n name: 'atc',\n description: 'Run ABAP Test Cockpit (ATC) checks',\n\n options: [\n { flags: '-p, --package <package>', description: 'Run ATC on package' },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run ATC on transport request (e.g., S0DK942970)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run ATC on objects listed in file (one URI per line)',\n },\n {\n flags: '--variant <variant>',\n description: 'ATC check variant (default: from system customizing)',\n },\n {\n flags: '--max-results <number>',\n description: 'Maximum number of results',\n default: '100',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, gitlab, sarif',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description: 'Output file (required for gitlab/sarif format)',\n },\n {\n flags: '--resolver <name>',\n description:\n 'Finding resolver plugin for path/line resolution (e.g., abapgit)',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n transport?: string;\n fromFile?: string;\n variant?: string;\n maxResults?: string;\n format?: OutputFormat;\n output?: string;\n resolver?: string;\n };\n\n // Validate mutually exclusive options\n const targetCount = [\n options.package,\n options.object,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for gitlab/sarif formats\n if (\n (options.format === 'gitlab' || options.format === 'sarif') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client from context\n if (!ctx.getAdtClient) {\n ctx.logger.error(\n '❌ ADT client not available. This command requires authentication.',\n );\n ctx.logger.error(' Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n ctx.logger.info('🔍 Running ABAP Test Cockpit checks...');\n\n // Determine target(s)\n let targetUris: string[];\n let targetName: string;\n\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n targetUris = fileContent\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#')); // Skip empty lines and comments\n\n if (targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n } else if (options.transport) {\n targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];\n targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n } else if (options.package) {\n targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];\n targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n } else {\n targetUris = [options.object!];\n targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n }\n\n // Step 1: Get ATC customizing to find default check variant\n ctx.logger.info('📋 Reading ATC customizing...');\n const customizing = await client.adt.atc.customizing.get();\n\n // Find systemCheckVariant in properties\n let checkVariant = options.variant;\n if (!checkVariant) {\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n if (variantProp?.value) {\n checkVariant = variantProp.value;\n } else {\n checkVariant = 'DEFAULT';\n }\n }\n ctx.logger.info(`🎯 Check variant: ${checkVariant}`);\n\n // Step 2: Create worklist (just with checkVariant, no objects)\n ctx.logger.info('📋 Creating ATC worklist...');\n const worklistCreateResponse = await client.adt.atc.worklists.create({\n checkVariant,\n });\n\n // Extract worklist ID - SAP can return plain string or object\n let worklistId: string;\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = worklistCreateResponse.match(/id=\"([^\"]+)\"/);\n worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();\n } else if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n worklistId = worklistCreateResponse.worklist.id;\n } else if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n worklistId = worklistCreateResponse.worklistRun.worklistId;\n } else {\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n }\n ctx.logger.info(`📋 Worklist created: ${worklistId}`);\n\n // Step 3: Run ATC checks with objects\n ctx.logger.info('⏳ Running ATC analysis...');\n const maxResults = parseInt(options.maxResults || '100', 10);\n const runData = {\n run: {\n maximumVerdicts: maxResults,\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n await client.adt.atc.runs.post({ worklistId }, runData);\n\n // Step 4: Get results\n ctx.logger.info('📊 Fetching results...');\n const worklistResult = await client.adt.atc.worklists.get(worklistId, {\n includeExemptedFindings: 'false',\n });\n\n // Convert to AtcResult\n const result = convertWorklistToResult(\n worklistResult as WorklistResponse,\n checkVariant,\n );\n\n // Load finding resolver if requested\n let resolver: import('../types').FindingResolver | undefined;\n if (options.resolver) {\n const name = options.resolver;\n if (name === 'abapgit') {\n // Built-in abapgit resolver — no external dependencies\n const { createAbapGitResolver } = await import('../resolvers/abapgit');\n resolver = createAbapGitResolver();\n } else {\n // External resolver — try dynamic import\n try {\n ctx.logger.info(`📂 Loading external resolver: ${name}`);\n const mod = await import(name);\n if (typeof mod.createFindingResolver === 'function') {\n resolver = mod.createFindingResolver();\n } else {\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n }\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n }\n }\n\n // Output based on format\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'gitlab' && options.output) {\n await outputGitLabCodeQuality(result, options.output, { resolver });\n } else if (options.format === 'sarif' && options.output) {\n await outputSarifReport(result, options.output, targetName);\n } else {\n displayAtcResults(result, ctx.adtSystemName);\n }\n\n // Exit with error code if there are errors\n if (result.errorCount > 0) {\n process.exit(1);\n }\n },\n};\n\nexport default atcCommand;\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { CliCommandPlugin } from "@abapify/adt-plugin";
2
+
3
+ //#region src/commands/atc.d.ts
4
+ /**
5
+ * ATC Command Plugin
6
+ */
7
+ declare const atcCommand: CliCommandPlugin;
8
+ //#endregion
9
+ export { atcCommand, atcCommand as default };
10
+ //# sourceMappingURL=atc.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;;;cAoMa,UAAA,EAAY,gBAAA"}
@@ -0,0 +1,2 @@
1
+ import { t as atcCommand } from "../atc-DWwSD88H.mjs";
2
+ export { atcCommand, atcCommand as default };
@@ -0,0 +1,110 @@
1
+ import { atcCommand } from "./commands/atc.mjs";
2
+ import { CliCommandPlugin } from "@abapify/adt-plugin";
3
+
4
+ //#region src/commands/atc-customizing.d.ts
5
+ declare const atcCustomizingCommand: CliCommandPlugin;
6
+ //#endregion
7
+ //#region src/types.d.ts
8
+ /**
9
+ * ATC Types
10
+ *
11
+ * Types for ATC command and formatters.
12
+ */
13
+ /**
14
+ * ATC Finding with flattened object info
15
+ */
16
+ interface AtcFinding {
17
+ checkId: string;
18
+ checkTitle: string;
19
+ messageId: string;
20
+ priority: number;
21
+ messageText: string;
22
+ objectUri: string;
23
+ objectType: string;
24
+ objectName: string;
25
+ location?: string;
26
+ findingUri?: string;
27
+ }
28
+ /**
29
+ * ATC Result summary
30
+ */
31
+ interface AtcResult {
32
+ checkVariant: string;
33
+ totalFindings: number;
34
+ errorCount: number;
35
+ warningCount: number;
36
+ infoCount: number;
37
+ findings: AtcFinding[];
38
+ }
39
+ /**
40
+ * Output format options
41
+ */
42
+ type OutputFormat = 'console' | 'json' | 'gitlab' | 'sarif';
43
+ /**
44
+ * Resolved location for a finding in the git repository
45
+ */
46
+ interface ResolvedLocation {
47
+ /** Git-relative file path (e.g., src/zpackage/zpackage_clas/zcl_foo.clas.abap) */
48
+ path: string;
49
+ /** File-relative line number (1-based) */
50
+ line: number;
51
+ }
52
+ /**
53
+ * Finding resolver interface
54
+ *
55
+ * Resolves ATC finding locations (object type/name + method-relative line)
56
+ * to actual git file paths and file-relative line numbers.
57
+ *
58
+ * Implementations are format-specific (e.g., abapgit with FULL/PREFIX folder logic).
59
+ * The resolver is optional — without it, formatters use raw ATC data.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Load resolver from a plugin
64
+ * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';
65
+ * const resolver = await createFindingResolver();
66
+ *
67
+ * // Use with gitlab formatter
68
+ * await outputGitLabCodeQuality(result, outputFile, { resolver });
69
+ * ```
70
+ */
71
+ interface FindingResolver {
72
+ /**
73
+ * Resolve a finding's location to a git file path and file-relative line number.
74
+ *
75
+ * @param objectType - ABAP object type (e.g., 'CLAS', 'INTF', 'PROG')
76
+ * @param objectName - ABAP object name (e.g., 'ZCL_MY_CLASS')
77
+ * @param atcLine - Method-relative line number from ATC
78
+ * @param methodName - Method name (extracted from ATC location URI), if known
79
+ * @returns Resolved path and line, or null if resolution not possible
80
+ */
81
+ resolve(objectType: string, objectName: string, atcLine: number, methodName?: string): Promise<ResolvedLocation | null>;
82
+ }
83
+ //#endregion
84
+ //#region src/formatters/sarif.d.ts
85
+ declare function outputSarifReport(result: AtcResult, outputFile: string, _targetName?: string): Promise<void>;
86
+ //#endregion
87
+ //#region src/formatters/gitlab.d.ts
88
+ /**
89
+ * Options for the GitLab Code Quality formatter
90
+ */
91
+ interface GitLabFormatterOptions {
92
+ /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */
93
+ resolver?: FindingResolver;
94
+ }
95
+ declare function outputGitLabCodeQuality(result: AtcResult, outputFile: string, options?: GitLabFormatterOptions): Promise<void>;
96
+ //#endregion
97
+ //#region src/resolvers/abapgit.d.ts
98
+ /**
99
+ * Create a built-in abapGit finding resolver.
100
+ *
101
+ * Scans src/ to build a filename → git-path lookup, then resolves
102
+ * ATC object references to actual file paths and converts method-relative
103
+ * line numbers to file-relative.
104
+ *
105
+ * @param srcRoot - Path to scan for source files (default: 'src/')
106
+ */
107
+ declare function createAbapGitResolver(srcRoot?: string): FindingResolver;
108
+ //#endregion
109
+ export { type AtcFinding, type AtcResult, type FindingResolver, type OutputFormat, type ResolvedLocation, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
110
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/commands/atc-customizing.ts","../src/types.ts","../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/resolvers/abapgit.ts"],"mappings":";;;;cAiCa,qBAAA,EAAuB,gBAAA;;;;;;;AAApC;;;;UCxBiB,UAAA;EACf,OAAA;EACA,UAAA;EACA,SAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,UAAA;EACA,UAAA;EACA,QAAA;EACA,UAAA;AAAA;;;;UAMe,SAAA;EACf,YAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,SAAA;EACA,QAAA,EAAU,UAAA;AAAA;;;;KAMA,YAAA;;;;UAKK,gBAAA;EAXK;EAapB,IAAA;EAPU;EASV,IAAA;AAAA;;;AAJF;;;;;AA0BA;;;;;;;;;;;;UAAiB,eAAA;;;;AC3DjB;;;;;;EDqEE,OAAA,CACE,UAAA,UACA,UAAA,UACA,OAAA,UACA,UAAA,YACC,OAAA,CAAQ,gBAAA;AAAA;;;iBC1ES,iBAAA,CACpB,MAAA,EAAQ,SAAA,EACR,UAAA,UACA,WAAA,YACC,OAAA;;;;;ADYH;UEOiB,sBAAA;;EAEf,QAAA,GAAW,eAAA;AAAA;AAAA,iBAsBS,uBAAA,CACpB,MAAA,EAAQ,SAAA,EACR,UAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA;;;;;AFnDH;;;;;;;iBGmIgB,qBAAA,CAAsB,OAAA,YAAmB,eAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,47 @@
1
+ import { n as createAbapGitResolver } from "./abapgit-DfddrbJx.mjs";
2
+ import { n as outputGitLabCodeQuality, r as outputSarifReport, t as atcCommand } from "./atc-DWwSD88H.mjs";
3
+ //#region src/commands/atc-customizing.ts
4
+ const atcCustomizingCommand = {
5
+ name: "atc-customizing",
6
+ description: "Display ATC customizing settings (active variant, CI mode)",
7
+ options: [{
8
+ flags: "--json",
9
+ description: "Output as JSON"
10
+ }],
11
+ async execute(args, ctx) {
12
+ const options = args;
13
+ if (!ctx.getAdtClient) {
14
+ ctx.logger.error("❌ ADT client not available. Run: adt auth login");
15
+ process.exit(1);
16
+ }
17
+ const client = await ctx.getAdtClient();
18
+ try {
19
+ const properties = (await client.adt.atc.customizing.get())?.customizing?.properties?.property ?? [];
20
+ const settings = {};
21
+ for (const prop of properties) settings[prop.name] = prop.value ?? "";
22
+ if (options.json) {
23
+ console.log(JSON.stringify(settings, null, 2));
24
+ return;
25
+ }
26
+ const variant = settings["checkVariant"] ?? settings["defaultCheckVariant"] ?? "(not set)";
27
+ const ciMode = settings["ciMode"] ?? settings["CIMode"] ?? "(not set)";
28
+ console.log("📋 ATC Customizing Settings");
29
+ console.log("─".repeat(40));
30
+ console.log(` Active check variant: ${variant}`);
31
+ console.log(` CI mode: ${ciMode}`);
32
+ console.log("");
33
+ if (Object.keys(settings).length > 0) {
34
+ console.log(" All properties:");
35
+ for (const [key, value] of Object.entries(settings)) console.log(` ${key}: ${value}`);
36
+ }
37
+ } catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ ctx.logger.error(`❌ Failed to get ATC customizing: ${message}`);
40
+ process.exit(1);
41
+ }
42
+ }
43
+ };
44
+ //#endregion
45
+ export { atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
46
+
47
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/commands/atc-customizing.ts"],"sourcesContent":["/**\n * ATC Customizing Command Plugin\n *\n * CLI command for displaying ATC customizing settings.\n *\n * Usage:\n * adt atc-customizing\n * adt atc-customizing --json\n *\n * This command reads from /sap/bc/adt/atc/customizing and displays:\n * - Active check variant\n * - CI mode setting\n * - Exemption reasons\n *\n * Mirrors sapcli's `atc customizing` command.\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\n\ninterface AdtClient {\n adt: {\n atc: {\n customizing: {\n get: () => Promise<{\n customizing: {\n properties: { property?: Array<{ name: string; value?: string }> };\n };\n }>;\n };\n };\n };\n}\n\nexport const atcCustomizingCommand: CliCommandPlugin = {\n name: 'atc-customizing',\n description: 'Display ATC customizing settings (active variant, CI mode)',\n\n options: [\n {\n flags: '--json',\n description: 'Output as JSON',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as { json?: boolean };\n\n if (!ctx.getAdtClient) {\n ctx.logger.error('❌ ADT client not available. Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n try {\n const response = await client.adt.atc.customizing.get();\n const properties = response?.customizing?.properties?.property ?? [];\n\n const settings: Record<string, string> = {};\n for (const prop of properties) {\n settings[prop.name] = prop.value ?? '';\n }\n\n if (options.json) {\n console.log(JSON.stringify(settings, null, 2));\n return;\n }\n\n // Human-readable output\n const variant =\n settings['checkVariant'] ??\n settings['defaultCheckVariant'] ??\n '(not set)';\n const ciMode = settings['ciMode'] ?? settings['CIMode'] ?? '(not set)';\n\n console.log('📋 ATC Customizing Settings');\n console.log('─'.repeat(40));\n console.log(` Active check variant: ${variant}`);\n console.log(` CI mode: ${ciMode}`);\n console.log('');\n if (Object.keys(settings).length > 0) {\n console.log(' All properties:');\n for (const [key, value] of Object.entries(settings)) {\n console.log(` ${key}: ${value}`);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n ctx.logger.error(`❌ Failed to get ATC customizing: ${message}`);\n process.exit(1);\n }\n },\n};\n\nexport default atcCustomizingCommand;\n"],"mappings":";;;AAiCA,MAAa,wBAA0C;CACrD,MAAM;CACN,aAAa;CAEb,SAAS,CACP;EACE,OAAO;EACP,aAAa;EACd,CACF;CAED,MAAM,QAAQ,MAAM,KAAiB;EACnC,MAAM,UAAU;AAEhB,MAAI,CAAC,IAAI,cAAc;AACrB,OAAI,OAAO,MAAM,kDAAkD;AACnE,WAAQ,KAAK,EAAE;;EAGjB,MAAM,SAAU,MAAM,IAAI,cAAc;AAExC,MAAI;GAEF,MAAM,cADW,MAAM,OAAO,IAAI,IAAI,YAAY,KAAK,GAC1B,aAAa,YAAY,YAAY,EAAE;GAEpE,MAAM,WAAmC,EAAE;AAC3C,QAAK,MAAM,QAAQ,WACjB,UAAS,KAAK,QAAQ,KAAK,SAAS;AAGtC,OAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;AAC9C;;GAIF,MAAM,UACJ,SAAS,mBACT,SAAS,0BACT;GACF,MAAM,SAAS,SAAS,aAAa,SAAS,aAAa;AAE3D,WAAQ,IAAI,8BAA8B;AAC1C,WAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;AAC3B,WAAQ,IAAI,2BAA2B,UAAU;AACjD,WAAQ,IAAI,2BAA2B,SAAS;AAChD,WAAQ,IAAI,GAAG;AACf,OAAI,OAAO,KAAK,SAAS,CAAC,SAAS,GAAG;AACpC,YAAQ,IAAI,oBAAoB;AAChC,SAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CACjD,SAAQ,IAAI,OAAO,IAAI,IAAI,QAAQ;;WAGhC,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,OAAI,OAAO,MAAM,oCAAoC,UAAU;AAC/D,WAAQ,KAAK,EAAE;;;CAGpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-atc",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "ABAP Test Cockpit (ATC) CLI plugin for adt-cli",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -28,7 +28,7 @@
28
28
  "gitlab"
29
29
  ],
30
30
  "dependencies": {
31
- "@abapify/adt-plugin": "0.4.0",
31
+ "@abapify/adt-plugin": "0.4.1",
32
32
  "chalk": "^5.3.0"
33
33
  },
34
34
  "module": "./dist/index.mjs",