@abapify/adt-atc 0.4.1 → 0.4.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/README.md CHANGED
@@ -26,7 +26,7 @@ Then use the command:
26
26
 
27
27
  ```bash
28
28
  # Run ATC on a transport
29
- adt atc -t S0DK942970
29
+ adt atc -t TRLK942970
30
30
 
31
31
  # Run ATC on a package
32
32
  adt atc -p ZMY_PACKAGE
@@ -35,10 +35,10 @@ adt atc -p ZMY_PACKAGE
35
35
  adt atc -o /sap/bc/adt/oo/classes/zcl_my_class
36
36
 
37
37
  # Output as SARIF (for GitHub Code Scanning)
38
- adt atc -t S0DK942970 --format sarif --output atc-results.sarif
38
+ adt atc -t TRLK942970 --format sarif --output atc-results.sarif
39
39
 
40
40
  # Output as GitLab Code Quality
41
- adt atc -t S0DK942970 --format gitlab --output gl-code-quality.json
41
+ adt atc -t TRLK942970 --format gitlab --output gl-code-quality.json
42
42
  ```
43
43
 
44
44
  ### Options
@@ -52,6 +52,7 @@ adt atc -t S0DK942970 --format gitlab --output gl-code-quality.json
52
52
  | `--max-results <number>` | Maximum number of results (default: 100) |
53
53
  | `--format <format>` | Output format: console, json, gitlab, sarif |
54
54
  | `--output <file>` | Output file (required for gitlab/sarif format) |
55
+ | `--resolver <name>` | Resolve report paths (built-in: `abapgit`) |
55
56
 
56
57
  ### Programmatic Usage
57
58
 
@@ -107,6 +108,14 @@ Raw JSON output of the ATC results for further processing.
107
108
 
108
109
  [GitLab Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) format for GitLab CI/CD integration.
109
110
 
111
+ For abapGit repositories, add `--resolver abapgit`. The resolver uses the
112
+ full ATC location URI to map compound objects such as function modules to
113
+ their serialized filenames, then scans the configured source tree so both
114
+ PREFIX and FULL folder logic point to the actual Git path. When multiple
115
+ files share the same basename (e.g. multiple `package.devc.xml` files in
116
+ different package directories), the resolver rejects the ambiguous match
117
+ rather than guessing.
118
+
110
119
  ## License
111
120
 
112
121
  MIT
@@ -0,0 +1,351 @@
1
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
+ import { basename, join, relative, resolve, 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/adt-uri-to-abapgit-path.ts
16
+ /**
17
+ * Convert an ADT URI to an on-disk abapGit filename.
18
+ *
19
+ * This mapper belongs next to the ATC resolver because ATC findings carry
20
+ * ADT locations while the GitLab report needs an abapGit file name. The
21
+ * abapGit format plugin re-exports it for consumers that already use that
22
+ * package.
23
+ */
24
+ /** Apply abapGit namespace transform: `/MYNS/zcl_foo` → `(myns)zcl_foo`. */
25
+ function applyNamespace(raw) {
26
+ const nsMatch = /^\/([^/]+)\/(.*)$/.exec(raw);
27
+ if (nsMatch) {
28
+ const [, ns, rest] = nsMatch;
29
+ return `(${ns.toLowerCase()})${rest.toLowerCase()}`;
30
+ }
31
+ return raw.toLowerCase();
32
+ }
33
+ function stripFragment(uri) {
34
+ const hashIndex = uri.indexOf("#");
35
+ return hashIndex >= 0 ? uri.slice(0, hashIndex) : uri;
36
+ }
37
+ const CLASS_INCLUDE_TO_SUFFIX = {
38
+ testclasses: "testclasses",
39
+ definitions: "locals_def",
40
+ implementations: "locals_imp",
41
+ macros: "macros",
42
+ localtypes: "locals_types"
43
+ };
44
+ const RULES = [
45
+ {
46
+ test: /^\/sap\/bc\/adt\/oo\/classes\/([^/]+)\/includes\/([^/]+)$/,
47
+ build: (match) => {
48
+ const name = applyNamespace(decodeURIComponent(match[1]));
49
+ const include = match[2].toLowerCase();
50
+ if (include === "main") return `src/${name}.clas.abap`;
51
+ const suffix = CLASS_INCLUDE_TO_SUFFIX[include];
52
+ return suffix ? `src/${name}.clas.${suffix}.abap` : `src/${name}.clas.${include}.abap`;
53
+ }
54
+ },
55
+ {
56
+ test: /^\/sap\/bc\/adt\/oo\/classes\/([^/]+)\/source\/main$/,
57
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.clas.abap`
58
+ },
59
+ {
60
+ test: /^\/sap\/bc\/adt\/oo\/classes\/([^/]+)$/,
61
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.clas.abap`
62
+ },
63
+ {
64
+ test: /^\/sap\/bc\/adt\/oo\/interfaces\/([^/]+?)(?:\/source\/main)?$/,
65
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.intf.abap`
66
+ },
67
+ {
68
+ test: /^\/sap\/bc\/adt\/programs\/programs\/([^/]+?)(?:\/source\/main)?$/,
69
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.prog.abap`
70
+ },
71
+ {
72
+ test: /^\/sap\/bc\/adt\/functions\/groups\/([^/]+)\/source\/main$/,
73
+ build: (match) => {
74
+ const group = applyNamespace(decodeURIComponent(match[1]));
75
+ return `src/${group}.fugr.l${group}top.abap`;
76
+ }
77
+ },
78
+ {
79
+ test: /^\/sap\/bc\/adt\/functions\/groups\/([^/]+)$/,
80
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.fugr.xml`
81
+ },
82
+ {
83
+ test: /^\/sap\/bc\/adt\/functions\/groups\/([^/]+)\/fmodules\/([^/]+?)(?:\/source\/main)?$/,
84
+ build: (match) => {
85
+ return `src/${applyNamespace(decodeURIComponent(match[1]))}.fugr.${applyNamespace(decodeURIComponent(match[2]))}.abap`;
86
+ }
87
+ },
88
+ {
89
+ test: /^\/sap\/bc\/adt\/functions\/groups\/([^/]+)\/includes\/([^/]+?)(?:\/source\/main)?$/,
90
+ build: (match) => {
91
+ return `src/${applyNamespace(decodeURIComponent(match[1]))}.fugr.${applyNamespace(decodeURIComponent(match[2]))}.abap`;
92
+ }
93
+ },
94
+ {
95
+ test: /^\/sap\/bc\/adt\/ddic\/ddl\/sources\/([^/]+?)(?:\/source\/main)?$/,
96
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.ddls.acds`
97
+ },
98
+ {
99
+ test: /^\/sap\/bc\/adt\/acm\/dcl\/sources\/([^/]+?)(?:\/source\/main)?$/,
100
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.dcls.acds`
101
+ },
102
+ {
103
+ test: /^\/sap\/bc\/adt\/bo\/behaviordefinitions\/([^/]+?)(?:\/source\/main)?$/,
104
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.bdef.abdl`
105
+ },
106
+ {
107
+ test: /^\/sap\/bc\/adt\/ddic\/srvd\/sources\/([^/]+?)(?:\/source\/main)?$/,
108
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.srvd.acds`
109
+ },
110
+ {
111
+ test: /^\/sap\/bc\/adt\/businessservices\/bindings\/([^/]+?)(?:\/publishedstates)?$/,
112
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.srvb.xml`
113
+ },
114
+ {
115
+ test: /^\/sap\/bc\/adt\/ddic\/domains\/([^/]+)$/,
116
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.doma.xml`
117
+ },
118
+ {
119
+ test: /^\/sap\/bc\/adt\/ddic\/dataelements\/([^/]+)$/,
120
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.dtel.xml`
121
+ },
122
+ {
123
+ test: /^\/sap\/bc\/adt\/ddic\/(?:structures|tables)\/([^/]+)$/,
124
+ build: (match) => `src/${applyNamespace(decodeURIComponent(match[1]))}.tabl.xml`
125
+ },
126
+ {
127
+ test: /^\/sap\/bc\/adt\/packages\/([^/]+)$/,
128
+ build: () => "src/package.devc.xml"
129
+ }
130
+ ];
131
+ /**
132
+ * Convert an ADT URI to the abapGit on-disk filename. The returned path is
133
+ * rooted at the configured abapGit starting folder (`src/` by default);
134
+ * callers resolve that prefix against the repository's actual source tree.
135
+ */
136
+ function adtUriToAbapGitPath(uri) {
137
+ if (!uri) return null;
138
+ const clean = stripFragment(uri).trim();
139
+ for (const rule of RULES) {
140
+ const match = rule.test.exec(clean);
141
+ if (match) try {
142
+ return rule.build(match);
143
+ } catch (error) {
144
+ if (error instanceof URIError) return null;
145
+ throw error;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ //#endregion
151
+ //#region src/resolvers/abapgit.ts
152
+ /**
153
+ * Built-in abapGit Finding Resolver.
154
+ *
155
+ * ATC identifies a finding by an ADT URI, while GitLab needs the path of the
156
+ * serialized abapGit file. The resolver owns that format-specific mapping and
157
+ * scans the checked-out repository so PREFIX and FULL layouts both resolve to
158
+ * the path that actually exists in Git.
159
+ */
160
+ var abapgit_exports = /* @__PURE__ */ __exportAll({ createAbapGitResolver: () => createAbapGitResolver });
161
+ function toPosix(pathValue) {
162
+ return sep === "/" ? pathValue : pathValue.split(sep).join("/");
163
+ }
164
+ function normalizeStartingFolder(value) {
165
+ return (value || "src").trim().replaceAll("\\", "/").replace(/^\/+|\/+$/g, "") || "src";
166
+ }
167
+ function parseFolderLogic(value) {
168
+ const normalized = value?.trim().toLowerCase();
169
+ if (normalized === "prefix" || normalized === "full" || normalized === "full-with-root") return normalized;
170
+ }
171
+ function parseAbapGitMetadata(root) {
172
+ const metadataPath = join(root, ".abapgit.xml");
173
+ if (!existsSync(metadataPath)) return void 0;
174
+ try {
175
+ const xml = readFileSync(metadataPath, "utf8");
176
+ return {
177
+ folderLogic: parseFolderLogic(xml.match(/<FOLDER_LOGIC>\s*([^<]+?)\s*<\/FOLDER_LOGIC>/i)?.[1]),
178
+ startingFolder: normalizeStartingFolder(xml.match(/<STARTING_FOLDER>\s*([^<]+?)\s*<\/STARTING_FOLDER>/i)?.[1])
179
+ };
180
+ } catch {
181
+ return;
182
+ }
183
+ }
184
+ function parseConfiguredFormat(root) {
185
+ const configPaths = [
186
+ process.env.ADT_CONFIG_PATH,
187
+ join(root, "adt.config.ts"),
188
+ join(root, "adt.config.js"),
189
+ join(root, "adt.config.mjs")
190
+ ].filter((value) => Boolean(value));
191
+ for (const configPath of [...new Set(configPaths.map((value) => resolve(value)))]) {
192
+ if (!existsSync(configPath)) continue;
193
+ try {
194
+ const config = readFileSync(configPath, "utf8");
195
+ const formatId = config.match(/\bformat\s*:\s*\{[\s\S]*?\bid\s*:\s*['"]([^'"]+)['"]/i)?.[1];
196
+ const folderLogic = parseFolderLogic(config.match(/\bfolderLogic\s*:\s*['"]([^'"]+)['"]/i)?.[1]);
197
+ if (formatId || folderLogic) return {
198
+ ...formatId ? { format: formatId.trim().toLowerCase() } : {},
199
+ ...folderLogic ? { folderLogic } : {}
200
+ };
201
+ } catch {}
202
+ }
203
+ return {};
204
+ }
205
+ function findRepositoryRoot() {
206
+ const configuredRoot = process.env.CI_PROJECT_DIR?.trim() || process.env.SOURCE_REPO_DIR?.trim();
207
+ if (configuredRoot) return resolve(configuredRoot);
208
+ return process.cwd();
209
+ }
210
+ function resolveRepository(srcRoot) {
211
+ const usesConfiguredSourceRoot = srcRoot === "src/" || srcRoot === "src";
212
+ const root = usesConfiguredSourceRoot ? findRepositoryRoot() : process.cwd();
213
+ const metadata = parseAbapGitMetadata(root);
214
+ const configured = parseConfiguredFormat(root);
215
+ const configuredSourceFolder = metadata?.startingFolder ?? "src";
216
+ const sourcePath = usesConfiguredSourceRoot ? join(root, configuredSourceFolder) : resolve(process.cwd(), srcRoot);
217
+ const realRoot = existsSync(root) ? realpathSync(root) : root;
218
+ const realSourcePath = existsSync(sourcePath) ? realpathSync(sourcePath) : sourcePath;
219
+ return {
220
+ root,
221
+ sourceRoot: realSourcePath === realRoot || realSourcePath.startsWith(realRoot + sep) ? sourcePath : join(root, "src"),
222
+ metadata,
223
+ configuredFormat: configured.format,
224
+ configuredFolderLogic: configured.folderLogic
225
+ };
226
+ }
227
+ const fileCache = /* @__PURE__ */ new Map();
228
+ const SOURCE_EXTENSIONS = [
229
+ ".abap",
230
+ ".xml",
231
+ ".acds",
232
+ ".abdl"
233
+ ];
234
+ function hasSourceExtension(filename) {
235
+ return SOURCE_EXTENSIONS.some((ext) => filename.endsWith(ext));
236
+ }
237
+ function indexEntry(entry, current, result, stack) {
238
+ const fullPath = join(current, entry.name);
239
+ if (entry.isDirectory()) stack.push(fullPath);
240
+ else if (entry.isFile() && hasSourceExtension(entry.name)) result.push(fullPath);
241
+ }
242
+ function collectSourceFiles(root) {
243
+ const result = [];
244
+ const stack = [root];
245
+ while (stack.length > 0) {
246
+ const current = stack.pop();
247
+ if (!current) break;
248
+ let entries;
249
+ try {
250
+ entries = readdirSync(current, { withFileTypes: true });
251
+ } catch {
252
+ continue;
253
+ }
254
+ for (const entry of entries) indexEntry(entry, current, result, stack);
255
+ }
256
+ return result;
257
+ }
258
+ function getFileLines(filePath) {
259
+ const cached = fileCache.get(filePath);
260
+ if (cached) return cached;
261
+ try {
262
+ const lines = readFileSync(filePath, "utf8").split("\n");
263
+ fileCache.set(filePath, lines);
264
+ return lines;
265
+ } catch {
266
+ return null;
267
+ }
268
+ }
269
+ function parseMethodRanges(lines) {
270
+ const ranges = [];
271
+ let currentMethod = null;
272
+ let methodStart = 0;
273
+ for (let i = 0; i < lines.length; i++) {
274
+ const match = lines[i].match(/^\s*METHOD\s+(\w+)/i);
275
+ if (match) {
276
+ currentMethod = match[1].toLowerCase();
277
+ methodStart = i + 1;
278
+ }
279
+ if (currentMethod && /^\s*ENDMETHOD/i.test(lines[i])) {
280
+ const endLine = i + 1;
281
+ ranges.push({
282
+ name: currentMethod,
283
+ startLine: methodStart,
284
+ length: endLine - methodStart + 1
285
+ });
286
+ currentMethod = null;
287
+ }
288
+ }
289
+ return ranges;
290
+ }
291
+ function convertLine(atcLine, methodName, filePath) {
292
+ if (!filePath.endsWith(".clas.abap")) return atcLine;
293
+ const lines = getFileLines(filePath);
294
+ if (!lines) return atcLine;
295
+ const ranges = parseMethodRanges(lines);
296
+ if (ranges.length === 0) return atcLine;
297
+ if (methodName) {
298
+ const method = ranges.find((range) => range.name === methodName.toLowerCase());
299
+ if (method) return method.startLine + atcLine - 1;
300
+ }
301
+ return atcLine;
302
+ }
303
+ /**
304
+ * Create a built-in abapGit finding resolver.
305
+ *
306
+ * The full ATC location is used to derive the canonical abapGit basename.
307
+ * The actual checked-out path is then selected from the configured source
308
+ * tree, which naturally preserves PREFIX/FULL package directories and the
309
+ * repository's STARTING_FOLDER.
310
+ */
311
+ function createAbapGitResolver(srcRoot = "src/") {
312
+ const repository = resolveRepository(srcRoot);
313
+ const lookup = /* @__PURE__ */ new Map();
314
+ try {
315
+ if (existsSync(repository.sourceRoot)) {
316
+ const files = collectSourceFiles(repository.sourceRoot).sort((left, right) => left.localeCompare(right));
317
+ const ambiguous = /* @__PURE__ */ new Set();
318
+ for (const filePath of files) {
319
+ const name = basename(filePath).toLowerCase();
320
+ if (lookup.has(name)) ambiguous.add(name);
321
+ else lookup.set(name, filePath);
322
+ }
323
+ for (const name of ambiguous) lookup.delete(name);
324
+ }
325
+ } catch {}
326
+ if (lookup.size > 0) {
327
+ const metadata = repository.metadata;
328
+ const format = repository.configuredFormat ?? "abapgit";
329
+ const folderLogic = metadata?.folderLogic ?? repository.configuredFolderLogic ?? "prefix";
330
+ const startingFolder = metadata?.startingFolder ?? "src";
331
+ console.log(`📂 Finding resolver: ${lookup.size} files indexed (format=${format}, folderLogic=${folderLogic}, startingFolder=${startingFolder})`);
332
+ }
333
+ return { async resolve(objectType, objectName, atcLine, methodName, atcLocation) {
334
+ const candidateSegments = ((atcLocation ? adtUriToAbapGitPath(atcLocation) : null) ?? `${objectName.toLowerCase()}.${objectType.toLowerCase()}.abap`).split("/");
335
+ const expectedFilename = candidateSegments[candidateSegments.length - 1];
336
+ if (!expectedFilename) return null;
337
+ const resolvedPath = lookup.get(expectedFilename.toLowerCase());
338
+ if (!resolvedPath) return null;
339
+ const fileLine = convertLine(atcLine, methodName, resolvedPath);
340
+ const gitPath = toPosix(relative(repository.root, resolvedPath));
341
+ if (!gitPath || gitPath.startsWith("../")) return null;
342
+ return {
343
+ path: gitPath,
344
+ line: fileLine
345
+ };
346
+ } };
347
+ }
348
+ //#endregion
349
+ export { createAbapGitResolver as n, adtUriToAbapGitPath as r, abapgit_exports as t };
350
+
351
+ //# sourceMappingURL=abapgit-DNnBub1F.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abapgit-DNnBub1F.mjs","names":["resolvePath"],"sources":["../src/resolvers/adt-uri-to-abapgit-path.ts","../src/resolvers/abapgit.ts"],"sourcesContent":["/**\n * Convert an ADT URI to an on-disk abapGit filename.\n *\n * This mapper belongs next to the ATC resolver because ATC findings carry\n * ADT locations while the GitLab report needs an abapGit file name. The\n * abapGit format plugin re-exports it for consumers that already use that\n * package.\n */\n\n/** Apply abapGit namespace transform: `/MYNS/zcl_foo` → `(myns)zcl_foo`. */\nfunction applyNamespace(raw: string): string {\n const nsMatch = /^\\/([^/]+)\\/(.*)$/.exec(raw);\n if (nsMatch) {\n const [, ns, rest] = nsMatch;\n return `(${ns.toLowerCase()})${rest.toLowerCase()}`;\n }\n return raw.toLowerCase();\n}\n\nfunction stripFragment(uri: string): string {\n const hashIndex = uri.indexOf('#');\n return hashIndex >= 0 ? uri.slice(0, hashIndex) : uri;\n}\n\n// Class include → abapGit suffix. Kept here so the built-in ATC package does\n// not depend on the format plugin (which depends on @abapify/adt-atc).\n// `main` maps to the primary class source (no suffix).\nconst CLASS_INCLUDE_TO_SUFFIX: Record<string, string | undefined> = {\n testclasses: 'testclasses',\n definitions: 'locals_def',\n implementations: 'locals_imp',\n macros: 'macros',\n localtypes: 'locals_types',\n};\n\ninterface Rule {\n test: RegExp;\n build: (match: RegExpMatchArray) => string;\n}\n\n// Order matters – most specific first.\nconst RULES: Rule[] = [\n {\n test: /^\\/sap\\/bc\\/adt\\/oo\\/classes\\/([^/]+)\\/includes\\/([^/]+)$/,\n build: (match) => {\n const name = applyNamespace(decodeURIComponent(match[1]));\n const include = match[2].toLowerCase();\n if (include === 'main') return `src/${name}.clas.abap`;\n const suffix = CLASS_INCLUDE_TO_SUFFIX[include];\n return suffix\n ? `src/${name}.clas.${suffix}.abap`\n : `src/${name}.clas.${include}.abap`;\n },\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/oo\\/classes\\/([^/]+)\\/source\\/main$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.clas.abap`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/oo\\/classes\\/([^/]+)$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.clas.abap`,\n },\n\n // Interfaces\n {\n test: /^\\/sap\\/bc\\/adt\\/oo\\/interfaces\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.intf.abap`,\n },\n\n // Programs\n {\n test: /^\\/sap\\/bc\\/adt\\/programs\\/programs\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.prog.abap`,\n },\n\n // Function group metadata and source components\n {\n test: /^\\/sap\\/bc\\/adt\\/functions\\/groups\\/([^/]+)\\/source\\/main$/,\n build: (match) => {\n const group = applyNamespace(decodeURIComponent(match[1]));\n return `src/${group}.fugr.l${group}top.abap`;\n },\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/functions\\/groups\\/([^/]+)$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.fugr.xml`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/functions\\/groups\\/([^/]+)\\/fmodules\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) => {\n const group = applyNamespace(decodeURIComponent(match[1]));\n const functionModule = applyNamespace(decodeURIComponent(match[2]));\n return `src/${group}.fugr.${functionModule}.abap`;\n },\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/functions\\/groups\\/([^/]+)\\/includes\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) => {\n const group = applyNamespace(decodeURIComponent(match[1]));\n const include = applyNamespace(decodeURIComponent(match[2]));\n return `src/${group}.fugr.${include}.abap`;\n },\n },\n\n // CDS and RAP source objects\n {\n test: /^\\/sap\\/bc\\/adt\\/ddic\\/ddl\\/sources\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.ddls.acds`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/acm\\/dcl\\/sources\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.dcls.acds`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/bo\\/behaviordefinitions\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.bdef.abdl`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/ddic\\/srvd\\/sources\\/([^/]+?)(?:\\/source\\/main)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.srvd.acds`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/businessservices\\/bindings\\/([^/]+?)(?:\\/publishedstates)?$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.srvb.xml`,\n },\n\n // DDIC objects\n {\n test: /^\\/sap\\/bc\\/adt\\/ddic\\/domains\\/([^/]+)$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.doma.xml`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/ddic\\/dataelements\\/([^/]+)$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.dtel.xml`,\n },\n {\n test: /^\\/sap\\/bc\\/adt\\/ddic\\/(?:structures|tables)\\/([^/]+)$/,\n build: (match) =>\n `src/${applyNamespace(decodeURIComponent(match[1]))}.tabl.xml`,\n },\n\n // Package — abapGit serializes every package to the fixed filename\n // `package.devc.xml`, regardless of the package name.\n {\n test: /^\\/sap\\/bc\\/adt\\/packages\\/([^/]+)$/,\n build: () => 'src/package.devc.xml',\n },\n];\n\n/**\n * Convert an ADT URI to the abapGit on-disk filename. The returned path is\n * rooted at the configured abapGit starting folder (`src/` by default);\n * callers resolve that prefix against the repository's actual source tree.\n */\nexport function adtUriToAbapGitPath(uri: string): string | null {\n if (!uri) return null;\n const clean = stripFragment(uri).trim();\n for (const rule of RULES) {\n const match = rule.test.exec(clean);\n if (match) {\n try {\n return rule.build(match);\n } catch (error) {\n if (error instanceof URIError) return null;\n throw error;\n }\n }\n }\n return null;\n}\n","/**\n * Built-in abapGit Finding Resolver.\n *\n * ATC identifies a finding by an ADT URI, while GitLab needs the path of the\n * serialized abapGit file. The resolver owns that format-specific mapping and\n * scans the checked-out repository so PREFIX and FULL layouts both resolve to\n * the path that actually exists in Git.\n */\n\nimport {\n existsSync,\n readFileSync,\n readdirSync,\n realpathSync,\n type Dirent,\n} from 'node:fs';\nimport {\n basename,\n join,\n relative,\n resolve as resolvePath,\n sep,\n} from 'node:path';\nimport type { FindingResolver, ResolvedLocation } from '../types';\nimport { adtUriToAbapGitPath } from './adt-uri-to-abapgit-path';\n\ntype FolderLogic = 'prefix' | 'full' | 'full-with-root';\n\ninterface AbapGitMetadata {\n folderLogic?: FolderLogic;\n startingFolder: string;\n}\n\ninterface ResolverRepository {\n root: string;\n sourceRoot: string;\n metadata?: AbapGitMetadata;\n configuredFormat?: string;\n configuredFolderLogic?: FolderLogic;\n}\n\nfunction toPosix(pathValue: string): string {\n return sep === '/' ? pathValue : pathValue.split(sep).join('/');\n}\n\nfunction normalizeStartingFolder(value: string | undefined): string {\n const normalized = (value || 'src')\n .trim()\n .replaceAll('\\\\', '/')\n .replace(/^\\/+|\\/+$/g, '');\n return normalized || 'src';\n}\n\nfunction parseFolderLogic(value: string | undefined): FolderLogic | undefined {\n const normalized = value?.trim().toLowerCase();\n if (\n normalized === 'prefix' ||\n normalized === 'full' ||\n normalized === 'full-with-root'\n ) {\n return normalized;\n }\n return undefined;\n}\n\nfunction parseAbapGitMetadata(root: string): AbapGitMetadata | undefined {\n const metadataPath = join(root, '.abapgit.xml');\n if (!existsSync(metadataPath)) return undefined;\n\n try {\n const xml = readFileSync(metadataPath, 'utf8');\n return {\n folderLogic: parseFolderLogic(\n xml.match(/<FOLDER_LOGIC>\\s*([^<]+?)\\s*<\\/FOLDER_LOGIC>/i)?.[1],\n ),\n startingFolder: normalizeStartingFolder(\n xml.match(/<STARTING_FOLDER>\\s*([^<]+?)\\s*<\\/STARTING_FOLDER>/i)?.[1],\n ),\n };\n } catch {\n return undefined;\n }\n}\n\nfunction parseConfiguredFormat(root: string): {\n format?: string;\n folderLogic?: FolderLogic;\n} {\n const configPaths = [\n process.env.ADT_CONFIG_PATH,\n join(root, 'adt.config.ts'),\n join(root, 'adt.config.js'),\n join(root, 'adt.config.mjs'),\n ].filter((value): value is string => Boolean(value));\n\n for (const configPath of [\n ...new Set(configPaths.map((value) => resolvePath(value))),\n ]) {\n if (!existsSync(configPath)) continue;\n try {\n const config = readFileSync(configPath, 'utf8');\n const formatId = config.match(\n /\\bformat\\s*:\\s*\\{[\\s\\S]*?\\bid\\s*:\\s*['\"]([^'\"]+)['\"]/i,\n )?.[1];\n const folderLogic = parseFolderLogic(\n config.match(/\\bfolderLogic\\s*:\\s*['\"]([^'\"]+)['\"]/i)?.[1],\n );\n if (formatId || folderLogic) {\n return {\n ...(formatId ? { format: formatId.trim().toLowerCase() } : {}),\n ...(folderLogic ? { folderLogic } : {}),\n };\n }\n } catch {\n // An unreadable optional config must not disable report generation.\n }\n }\n return {};\n}\n\nfunction findRepositoryRoot(): string {\n const configuredRoot =\n process.env.CI_PROJECT_DIR?.trim() || process.env.SOURCE_REPO_DIR?.trim();\n if (configuredRoot) return resolvePath(configuredRoot);\n\n // Preserve the old API behaviour for callers that pass an explicit source\n // path: paths are still reported relative to the current working directory.\n return process.cwd();\n}\n\nfunction resolveRepository(srcRoot: string): ResolverRepository {\n const usesConfiguredSourceRoot = srcRoot === 'src/' || srcRoot === 'src';\n const root = usesConfiguredSourceRoot ? findRepositoryRoot() : process.cwd();\n const metadata = parseAbapGitMetadata(root);\n const configured = parseConfiguredFormat(root);\n const configuredSourceFolder = metadata?.startingFolder ?? 'src';\n const sourcePath = usesConfiguredSourceRoot\n ? join(root, configuredSourceFolder)\n : resolvePath(process.cwd(), srcRoot);\n\n // Guard against path traversal via STARTING_FOLDER — the resolved source\n // root must stay inside the repository root. Use realpath to resolve\n // symlinks before the containment check. If it escapes, fall back to\n // the default `src` folder. Guard existsSync before realpathSync so a\n // missing source folder does not throw and abort report generation.\n const realRoot = existsSync(root) ? realpathSync(root) : root;\n const realSourcePath = existsSync(sourcePath)\n ? realpathSync(sourcePath)\n : sourcePath;\n const safeSourcePath =\n realSourcePath === realRoot || realSourcePath.startsWith(realRoot + sep)\n ? sourcePath\n : join(root, 'src');\n\n return {\n root,\n sourceRoot: safeSourcePath,\n metadata,\n configuredFormat: configured.format,\n configuredFolderLogic: configured.folderLogic,\n };\n}\n\n// ── Source tree and file contents ───────────────────────────────────────\n\nconst fileCache = new Map<string, string[]>();\n\nconst SOURCE_EXTENSIONS = ['.abap', '.xml', '.acds', '.abdl'];\n\nfunction hasSourceExtension(filename: string): boolean {\n return SOURCE_EXTENSIONS.some((ext) => filename.endsWith(ext));\n}\n\nfunction indexEntry(\n entry: Dirent,\n current: string,\n result: string[],\n stack: string[],\n): void {\n const fullPath = join(current, entry.name);\n if (entry.isDirectory()) {\n stack.push(fullPath);\n } else if (entry.isFile() && hasSourceExtension(entry.name)) {\n result.push(fullPath);\n }\n}\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 if (!current) break;\n let entries: Dirent[];\n try {\n entries = readdirSync(current, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n indexEntry(entry, current, result, stack);\n }\n }\n\n return result;\n}\n\nfunction getFileLines(filePath: string): string[] | null {\n const cached = fileCache.get(filePath);\n if (cached) return cached;\n try {\n const lines = readFileSync(filePath, 'utf8').split('\\n');\n fileCache.set(filePath, lines);\n return lines;\n } catch {\n return null;\n }\n}\n\n// ── Method range parsing ────────────────────────────────────────────────\n\ninterface MethodRange {\n name: string;\n startLine: number;\n length: number;\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;\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 if (methodName) {\n const method = ranges.find(\n (range) => range.name === methodName.toLowerCase(),\n );\n if (method) return method.startLine + atcLine - 1;\n }\n\n // Without a method name the ATC line is already relative to the class\n // file, so return it as-is instead of guessing a method.\n return atcLine;\n}\n\n// ── Resolver factory ────────────────────────────────────────────────────\n\n/**\n * Create a built-in abapGit finding resolver.\n *\n * The full ATC location is used to derive the canonical abapGit basename.\n * The actual checked-out path is then selected from the configured source\n * tree, which naturally preserves PREFIX/FULL package directories and the\n * repository's STARTING_FOLDER.\n */\nexport function createAbapGitResolver(srcRoot = 'src/'): FindingResolver {\n const repository = resolveRepository(srcRoot);\n const lookup = new Map<string, string>();\n\n try {\n if (existsSync(repository.sourceRoot)) {\n const files = collectSourceFiles(repository.sourceRoot).sort(\n (left, right) => left.localeCompare(right),\n );\n\n const ambiguous = new Set<string>();\n for (const filePath of files) {\n const name = basename(filePath).toLowerCase();\n if (lookup.has(name)) {\n ambiguous.add(name);\n } else {\n lookup.set(name, filePath);\n }\n }\n for (const name of ambiguous) lookup.delete(name);\n }\n } catch {\n // Source scan failed — resolver will return null for all findings.\n }\n\n if (lookup.size > 0) {\n const metadata = repository.metadata;\n const format = repository.configuredFormat ?? 'abapgit';\n const folderLogic =\n metadata?.folderLogic ?? repository.configuredFolderLogic ?? 'prefix';\n const startingFolder = metadata?.startingFolder ?? 'src';\n console.log(\n `📂 Finding resolver: ${lookup.size} files indexed (format=${format}, folderLogic=${folderLogic}, startingFolder=${startingFolder})`,\n );\n }\n\n return {\n async resolve(\n objectType: string,\n objectName: string,\n atcLine: number,\n methodName?: string,\n atcLocation?: string,\n ): Promise<ResolvedLocation | null> {\n const canonicalPath = atcLocation\n ? adtUriToAbapGitPath(atcLocation)\n : null;\n const candidatePath =\n canonicalPath ??\n `${objectName.toLowerCase()}.${objectType.toLowerCase()}.abap`;\n const candidateSegments = candidatePath.split('/');\n const expectedFilename = candidateSegments[candidateSegments.length - 1];\n if (!expectedFilename) return null;\n const resolvedPath = lookup.get(expectedFilename.toLowerCase());\n\n if (!resolvedPath) return null;\n\n const fileLine = convertLine(atcLine, methodName, resolvedPath);\n const gitPath = toPosix(relative(repository.root, resolvedPath));\n if (!gitPath || gitPath.startsWith('../')) return null;\n\n return { path: gitPath, line: fileLine };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import chalk from "chalk";
2
- import { writeFile } from "fs/promises";
2
+ import { writeFile } from "node:fs/promises";
3
3
  //#region src/formatters/sarif.ts
4
4
  /**
5
5
  * SARIF Formatter
@@ -21,7 +21,7 @@ async function outputSarifReport(result, outputFile, _targetName) {
21
21
  const results = result.findings.map((finding) => {
22
22
  const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;
23
23
  const lineMatch = finding.location?.match(/start=(\d+)/);
24
- const line = lineMatch ? parseInt(lineMatch[1], 10) : 1;
24
+ const line = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;
25
25
  return {
26
26
  ruleId: finding.checkId,
27
27
  level: finding.priority === 1 ? "error" : finding.priority === 2 ? "warning" : "note",
@@ -102,17 +102,15 @@ async function outputGitLabCodeQuality(result, outputFile, options) {
102
102
  case 3:
103
103
  severity = "minor";
104
104
  break;
105
- default:
106
- severity = "info";
107
- break;
105
+ default: severity = "info";
108
106
  }
109
107
  const lineMatch = finding.location?.match(/start=(\d+)/);
110
- const atcLine = lineMatch ? parseInt(lineMatch[1], 10) : 1;
108
+ const atcLine = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;
111
109
  const methodName = extractMethodName(finding.location);
112
110
  let filePath;
113
111
  let fileLine;
114
112
  if (resolver) {
115
- const resolved = await resolver.resolve(finding.objectType, finding.objectName, atcLine, methodName);
113
+ const resolved = await resolver.resolve(finding.objectType, finding.objectName, atcLine, methodName, finding.location);
116
114
  if (resolved) {
117
115
  filePath = resolved.path;
118
116
  fileLine = resolved.line;
@@ -177,7 +175,7 @@ function convertWorklistToResult(worklistResponse, checkVariant) {
177
175
  checkId: finding.checkId || "",
178
176
  checkTitle: finding.checkTitle || "",
179
177
  messageId: finding.messageId || "",
180
- priority: parseInt(finding.priority || "3", 10),
178
+ priority: Number.parseInt(finding.priority || "3", 10),
181
179
  messageText: finding.messageTitle || "",
182
180
  objectUri: obj.uri || "",
183
181
  objectType: obj.type || "",
@@ -244,7 +242,7 @@ const atcCommand = {
244
242
  },
245
243
  {
246
244
  flags: "-t, --transport <transport>",
247
- description: "Run ATC on transport request (e.g., S0DK942970)"
245
+ description: "Run ATC on transport request (e.g., TRLK942970)"
248
246
  },
249
247
  {
250
248
  flags: "-f, --from-file <file>",
@@ -348,7 +346,7 @@ const atcCommand = {
348
346
  ctx.logger.info(`📋 Worklist created: ${worklistId}`);
349
347
  ctx.logger.info("⏳ Running ATC analysis...");
350
348
  const runData = { run: {
351
- maximumVerdicts: parseInt(options.maxResults || "100", 10),
349
+ maximumVerdicts: Number.parseInt(options.maxResults || "100", 10),
352
350
  objectSets: { objectSet: [{
353
351
  kind: "inclusive",
354
352
  objectReferences: { objectReference: targetUris.map((uri) => ({ uri })) }
@@ -361,7 +359,7 @@ const atcCommand = {
361
359
  if (options.resolver) {
362
360
  const name = options.resolver;
363
361
  if (name === "abapgit") {
364
- const { createAbapGitResolver } = await import("./abapgit-DfddrbJx.mjs").then((n) => n.t);
362
+ const { createAbapGitResolver } = await import("./abapgit-DNnBub1F.mjs").then((n) => n.t);
365
363
  resolver = createAbapGitResolver();
366
364
  } else try {
367
365
  ctx.logger.info(`📂 Loading external resolver: ${name}`);
@@ -382,4 +380,4 @@ const atcCommand = {
382
380
  //#endregion
383
381
  export { outputGitLabCodeQuality as n, outputSarifReport as r, atcCommand as t };
384
382
 
385
- //# sourceMappingURL=atc-DWwSD88H.mjs.map
383
+ //# sourceMappingURL=atc-BwlD2kFI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atc-BwlD2kFI.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 'node: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 ? Number.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 'node: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 ? Number.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 finding.location,\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: Number.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., TRLK942970)',\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 = Number.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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"}
@@ -1,5 +1,4 @@
1
1
  import { CliCommandPlugin } from "@abapify/adt-plugin";
2
-
3
2
  //#region src/commands/atc.d.ts
4
3
  /**
5
4
  * ATC Command Plugin
@@ -1 +1 @@
1
- {"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;;;cAoMa,UAAA,EAAY,gBAAA"}
1
+ {"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;;cAoMa,YAAY"}
@@ -1,2 +1,2 @@
1
- import { t as atcCommand } from "../atc-DWwSD88H.mjs";
1
+ import { t as atcCommand } from "../atc-BwlD2kFI.mjs";
2
2
  export { atcCommand, atcCommand as default };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { atcCommand } from "./commands/atc.mjs";
2
2
  import { CliCommandPlugin } from "@abapify/adt-plugin";
3
-
4
3
  //#region src/commands/atc-customizing.d.ts
5
4
  declare const atcCustomizingCommand: CliCommandPlugin;
6
5
  //#endregion
@@ -76,9 +75,10 @@ interface FindingResolver {
76
75
  * @param objectName - ABAP object name (e.g., 'ZCL_MY_CLASS')
77
76
  * @param atcLine - Method-relative line number from ATC
78
77
  * @param methodName - Method name (extracted from ATC location URI), if known
78
+ * @param atcLocation - Full ATC location URI, if available
79
79
  * @returns Resolved path and line, or null if resolution not possible
80
80
  */
81
- resolve(objectType: string, objectName: string, atcLine: number, methodName?: string): Promise<ResolvedLocation | null>;
81
+ resolve(objectType: string, objectName: string, atcLine: number, methodName?: string, atcLocation?: string): Promise<ResolvedLocation | null>;
82
82
  }
83
83
  //#endregion
84
84
  //#region src/formatters/sarif.d.ts
@@ -98,13 +98,28 @@ declare function outputGitLabCodeQuality(result: AtcResult, outputFile: string,
98
98
  /**
99
99
  * Create a built-in abapGit finding resolver.
100
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/')
101
+ * The full ATC location is used to derive the canonical abapGit basename.
102
+ * The actual checked-out path is then selected from the configured source
103
+ * tree, which naturally preserves PREFIX/FULL package directories and the
104
+ * repository's STARTING_FOLDER.
106
105
  */
107
106
  declare function createAbapGitResolver(srcRoot?: string): FindingResolver;
108
107
  //#endregion
109
- export { type AtcFinding, type AtcResult, type FindingResolver, type OutputFormat, type ResolvedLocation, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
108
+ //#region src/resolvers/adt-uri-to-abapgit-path.d.ts
109
+ /**
110
+ * Convert an ADT URI to an on-disk abapGit filename.
111
+ *
112
+ * This mapper belongs next to the ATC resolver because ATC findings carry
113
+ * ADT locations while the GitLab report needs an abapGit file name. The
114
+ * abapGit format plugin re-exports it for consumers that already use that
115
+ * package.
116
+ */
117
+ /**
118
+ * Convert an ADT URI to the abapGit on-disk filename. The returned path is
119
+ * rooted at the configured abapGit starting folder (`src/` by default);
120
+ * callers resolve that prefix against the repository's actual source tree.
121
+ */
122
+ declare function adtUriToAbapGitPath(uri: string): string | null;
123
+ //#endregion
124
+ export { type AtcFinding, type AtcResult, type FindingResolver, type OutputFormat, type ResolvedLocation, adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
110
125
  //# sourceMappingURL=index.d.mts.map
@@ -1 +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"}
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","../src/resolvers/adt-uri-to-abapgit-path.ts"],"mappings":";;;cAiCa,uBAAuB;;;;;;;;;;;UCxBnB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;UAMe;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;;;;;KAMA;;;;UAKK;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;UAsBe;;;;;;;;;;;EAWf,QACE,oBACA,oBACA,iBACA,qBACA,uBACC,QAAQ;;;;iBC5ES,kBACpB,QAAQ,WACR,oBACA,uBACC;;;;;;UCmBc;;EAEf,WAAW;;iBAsBS,wBACpB,QAAQ,WACR,oBACA,UAAU,yBACT;;;;;;;;;;;iBCmOa,sBAAsB,mBAAmB;;;;;;;;;;;;;;;;iBCzHzC,oBAAoB"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as createAbapGitResolver } from "./abapgit-DfddrbJx.mjs";
2
- import { n as outputGitLabCodeQuality, r as outputSarifReport, t as atcCommand } from "./atc-DWwSD88H.mjs";
1
+ import { n as createAbapGitResolver, r as adtUriToAbapGitPath } from "./abapgit-DNnBub1F.mjs";
2
+ import { n as outputGitLabCodeQuality, r as outputSarifReport, t as atcCommand } from "./atc-BwlD2kFI.mjs";
3
3
  //#region src/commands/atc-customizing.ts
4
4
  const atcCustomizingCommand = {
5
5
  name: "atc-customizing",
@@ -42,6 +42,6 @@ const atcCustomizingCommand = {
42
42
  }
43
43
  };
44
44
  //#endregion
45
- export { atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
45
+ export { adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
46
46
 
47
47
  //# sourceMappingURL=index.mjs.map
@@ -1 +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"}
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;CACf,CACF;CAEA,MAAM,QAAQ,MAAM,KAAiB;EACnC,MAAM,UAAU;EAEhB,IAAI,CAAC,IAAI,cAAc;GACrB,IAAI,OAAO,MAAM,iDAAiD;GAClE,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,SAAU,MAAM,IAAI,aAAa;EAEvC,IAAI;GAEF,MAAM,cAAa,MADI,OAAO,IAAI,IAAI,YAAY,IAAI,EAAA,EACzB,aAAa,YAAY,YAAY,CAAC;GAEnE,MAAM,WAAmC,CAAC;GAC1C,KAAK,MAAM,QAAQ,YACjB,SAAS,KAAK,QAAQ,KAAK,SAAS;GAGtC,IAAI,QAAQ,MAAM;IAChB,QAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;IAC7C;GACF;GAGA,MAAM,UACJ,SAAS,mBACT,SAAS,0BACT;GACF,MAAM,SAAS,SAAS,aAAa,SAAS,aAAa;GAE3D,QAAQ,IAAI,6BAA6B;GACzC,QAAQ,IAAI,IAAI,OAAO,EAAE,CAAC;GAC1B,QAAQ,IAAI,2BAA2B,SAAS;GAChD,QAAQ,IAAI,2BAA2B,QAAQ;GAC/C,QAAQ,IAAI,EAAE;GACd,IAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,GAAG;IACpC,QAAQ,IAAI,mBAAmB;IAC/B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAChD,QAAQ,IAAI,OAAO,IAAI,IAAI,OAAO;GAEtC;EACF,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACrE,IAAI,OAAO,MAAM,oCAAoC,SAAS;GAC9D,QAAQ,KAAK,CAAC;EAChB;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-atc",
3
- "version": "0.4.1",
3
+ "version": "0.4.4",
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.1",
31
+ "@abapify/adt-plugin": "0.4.4",
32
32
  "chalk": "^5.3.0"
33
33
  },
34
34
  "module": "./dist/index.mjs",
@@ -1,135 +0,0 @@
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
@@ -1 +0,0 @@
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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"}
@@ -1 +0,0 @@
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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"}