@abapify/adt-atc 0.4.44 → 0.4.46

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.
@@ -188,7 +188,7 @@ function parseConfiguredFormat(root) {
188
188
  ].filter((value) => Boolean(value));
189
189
  const resolvedConfigPaths = /* @__PURE__ */ new Set();
190
190
  for (const value of configPaths) {
191
- if (value.includes("..")) continue;
191
+ if (/\.\./.test(value)) continue;
192
192
  resolvedConfigPaths.add(resolve(value));
193
193
  }
194
194
  for (const configPath of resolvedConfigPaths) {
@@ -208,7 +208,7 @@ function parseConfiguredFormat(root) {
208
208
  function findRepositoryRoot() {
209
209
  const configuredRoot = process.env.CI_PROJECT_DIR?.trim() || process.env.SOURCE_REPO_DIR?.trim();
210
210
  if (configuredRoot) {
211
- if (configuredRoot.includes("..")) throw new Error("Repository root must not contain path traversal segments");
211
+ if (/\.\./.test(configuredRoot)) throw new Error("Repository root must not contain path traversal segments");
212
212
  return resolve(configuredRoot);
213
213
  }
214
214
  return process.cwd();
@@ -352,4 +352,4 @@ function createAbapGitResolver(srcRoot = "src/") {
352
352
  }
353
353
  export { createAbapGitResolver as n, adtUriToAbapGitPath as r, abapgit_exports as t };
354
354
 
355
- //# sourceMappingURL=abapgit-C7MNIMrZ.mjs.map
355
+ //# sourceMappingURL=abapgit-D-WCS83I.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"abapgit-C7MNIMrZ.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(/^\\/+/, '')\n .replace(/(?<!\\/)\\/+$/, '');\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>([^<]*)<\\/FOLDER_LOGIC>/i)?.[1],\n ),\n startingFolder: normalizeStartingFolder(\n xml.match(/<STARTING_FOLDER>([^<]*)<\\/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 const resolvedConfigPaths = new Set<string>();\n for (const value of configPaths) {\n if (value.includes('..')) continue;\n resolvedConfigPaths.add(resolvePath(value));\n }\n\n for (const configPath of resolvedConfigPaths) {\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) {\n if (configuredRoot.includes('..')) {\n throw new Error(\n 'Repository root must not contain path traversal segments',\n );\n }\n return resolvePath(configuredRoot);\n }\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 = /^\\s*METHOD\\s+(\\w+)/i.exec(lines[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 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.at(-1);\n if (!expectedFilename) return Promise.resolve(null);\n const resolvedPath = lookup.get(expectedFilename.toLowerCase());\n\n if (!resolvedPath) return Promise.resolve(null);\n\n const fileLine = convertLine(atcLine, methodName, resolvedPath);\n const gitPath = toPosix(relative(repository.root, resolvedPath));\n if (!gitPath || gitPath.startsWith('../')) return Promise.resolve(null);\n\n return Promise.resolve({ path: gitPath, line: fileLine });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAUA,SAAS,eAAe,KAAqB;CAC3C,MAAM,UAAU,oBAAoB,KAAK,GAAG;CAC5C,IAAI,SAAS;EACX,MAAM,GAAG,IAAI,QAAQ;EACrB,OAAO,IAAI,GAAG,YAAY,EAAE,GAAG,KAAK,YAAY;CAClD;CACA,OAAO,IAAI,YAAY;AACzB;AAEA,SAAS,cAAc,KAAqB;CAC1C,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,OAAO,aAAa,IAAI,IAAI,MAAM,GAAG,SAAS,IAAI;AACpD;AAKA,MAAM,0BAA8D;CAClE,aAAa;CACb,aAAa;CACb,iBAAiB;CACjB,QAAQ;CACR,YAAY;AACd;AAQA,MAAM,QAAgB;CACpB;EACE,MAAM;EACN,QAAQ,UAAU;GAChB,MAAM,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC;GACxD,MAAM,UAAU,MAAM,EAAE,CAAC,YAAY;GACrC,IAAI,YAAY,QAAQ,OAAO,OAAO,KAAK;GAC3C,MAAM,SAAS,wBAAwB;GACvC,OAAO,SACH,OAAO,KAAK,QAAQ,OAAO,SAC3B,OAAO,KAAK,QAAQ,QAAQ;EAClC;CACF;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CAGA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CAGA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CAGA;EACE,MAAM;EACN,QAAQ,UAAU;GAChB,MAAM,QAAQ,eAAe,mBAAmB,MAAM,EAAE,CAAC;GACzD,OAAO,OAAO,MAAM,SAAS,MAAM;EACrC;CACF;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UAAU;GAGhB,OAAO,OAFO,eAAe,mBAAmB,MAAM,EAAE,CAEtC,EAAE,QADG,eAAe,mBAAmB,MAAM,EAAE,CACxB,EAAE;EAC7C;CACF;CACA;EACE,MAAM;EACN,QAAQ,UAAU;GAGhB,OAAO,OAFO,eAAe,mBAAmB,MAAM,EAAE,CAEtC,EAAE,QADJ,eAAe,mBAAmB,MAAM,EAAE,CACxB,EAAE;EACtC;CACF;CAGA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CAGA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CACA;EACE,MAAM;EACN,QAAQ,UACN,OAAO,eAAe,mBAAmB,MAAM,EAAE,CAAC,EAAE;CACxD;CAIA;EACE,MAAM;EACN,aAAa;CACf;AACF;;;;;;AAOA,SAAgB,oBAAoB,KAA4B;CAC9D,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,QAAQ,cAAc,GAAG,CAAC,CAAC,KAAK;CACtC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK;EAClC,IAAI,OACF,IAAI;GACF,OAAO,KAAK,MAAM,KAAK;EACzB,SAAS,OAAO;GACd,IAAI,iBAAiB,UAAU,OAAO;GACtC,MAAM;EACR;CAEJ;CACA,OAAO;AACT;;;;;;;;;;;AC5IA,SAAS,QAAQ,WAA2B;CAC1C,OAAO,QAAQ,MAAM,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;AAChE;AAEA,SAAS,wBAAwB,OAAmC;CAMlE,QALoB,SAAS,MAAA,CAC1B,KAAK,CAAC,CACN,WAAW,MAAM,GAAG,CAAC,CACrB,QAAQ,QAAQ,EAAE,CAAC,CACnB,QAAQ,eAAe,EACV,KAAK;AACvB;AAEA,SAAS,iBAAiB,OAAoD;CAC5E,MAAM,aAAa,OAAO,KAAK,CAAC,CAAC,YAAY;CAC7C,IACE,eAAe,YACf,eAAe,UACf,eAAe,kBAEf,OAAO;AAGX;AAEA,SAAS,qBAAqB,MAA2C;CACvE,MAAM,eAAe,KAAK,MAAM,cAAc;CAC9C,IAAI,CAAC,WAAW,YAAY,GAAG,OAAO,KAAA;CAEtC,IAAI;EACF,MAAM,MAAM,aAAa,cAAc,MAAM;EAC7C,OAAO;GACL,aAAa,iBACX,IAAI,MAAM,wCAAwC,CAAC,GAAG,EACxD;GACA,gBAAgB,wBACd,IAAI,MAAM,8CAA8C,CAAC,GAAG,EAC9D;EACF;CACF,QAAQ;EACN;CACF;AACF;AAEA,SAAS,sBAAsB,MAG7B;CACA,MAAM,cAAc;EAClB,QAAQ,IAAI;EACZ,KAAK,MAAM,eAAe;EAC1B,KAAK,MAAM,eAAe;EAC1B,KAAK,MAAM,gBAAgB;CAC7B,CAAC,CAAC,QAAQ,UAA2B,QAAQ,KAAK,CAAC;CAEnD,MAAM,sCAAsB,IAAI,IAAY;CAC5C,KAAK,MAAM,SAAS,aAAa;EAC/B,IAAI,MAAM,SAAS,IAAI,GAAG;EAC1B,oBAAoB,IAAIA,QAAY,KAAK,CAAC;CAC5C;CAEA,KAAK,MAAM,cAAc,qBAAqB;EAC5C,IAAI,CAAC,WAAW,UAAU,GAAG;EAC7B,IAAI;GACF,MAAM,SAAS,aAAa,YAAY,MAAM;GAC9C,MAAM,WAAW,OAAO,MACtB,uDACF,CAAC,GAAG;GACJ,MAAM,cAAc,iBAClB,OAAO,MAAM,uCAAuC,CAAC,GAAG,EAC1D;GACA,IAAI,YAAY,aACd,OAAO;IACL,GAAI,WAAW,EAAE,QAAQ,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC;IAC5D,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;GACvC;EAEJ,QAAQ,CAER;CACF;CACA,OAAO,CAAC;AACV;AAEA,SAAS,qBAA6B;CACpC,MAAM,iBACJ,QAAQ,IAAI,gBAAgB,KAAK,KAAK,QAAQ,IAAI,iBAAiB,KAAK;CAC1E,IAAI,gBAAgB;EAClB,IAAI,eAAe,SAAS,IAAI,GAC9B,MAAM,IAAI,MACR,0DACF;EAEF,OAAOA,QAAY,cAAc;CACnC;CAIA,OAAO,QAAQ,IAAI;AACrB;AAEA,SAAS,kBAAkB,SAAqC;CAC9D,MAAM,2BAA2B,YAAY,UAAU,YAAY;CACnE,MAAM,OAAO,2BAA2B,mBAAmB,IAAI,QAAQ,IAAI;CAC3E,MAAM,WAAW,qBAAqB,IAAI;CAC1C,MAAM,aAAa,sBAAsB,IAAI;CAC7C,MAAM,yBAAyB,UAAU,kBAAkB;CAC3D,MAAM,aAAa,2BACf,KAAK,MAAM,sBAAsB,IACjCA,QAAY,QAAQ,IAAI,GAAG,OAAO;CAOtC,MAAM,WAAW,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI;CACzD,MAAM,iBAAiB,WAAW,UAAU,IACxC,aAAa,UAAU,IACvB;CAMJ,OAAO;EACL;EACA,YANA,mBAAmB,YAAY,eAAe,WAAW,WAAW,GAAG,IACnE,aACA,KAAK,MAAM,KAAK;EAKpB;EACA,kBAAkB,WAAW;EAC7B,uBAAuB,WAAW;CACpC;AACF;AAIA,MAAM,4BAAY,IAAI,IAAsB;AAE5C,MAAM,oBAAoB;CAAC;CAAS;CAAQ;CAAS;AAAO;AAE5D,SAAS,mBAAmB,UAA2B;CACrD,OAAO,kBAAkB,MAAM,QAAQ,SAAS,SAAS,GAAG,CAAC;AAC/D;AAEA,SAAS,WACP,OACA,SACA,QACA,OACM;CACN,MAAM,WAAW,KAAK,SAAS,MAAM,IAAI;CACzC,IAAI,MAAM,YAAY,GACpB,MAAM,KAAK,QAAQ;MACd,IAAI,MAAM,OAAO,KAAK,mBAAmB,MAAM,IAAI,GACxD,OAAO,KAAK,QAAQ;AAExB;AAEA,SAAS,mBAAmB,MAAwB;CAClD,MAAM,SAAmB,CAAC;CAC1B,MAAM,QAAkB,CAAC,IAAI;CAE7B,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,UAAU,MAAM,IAAI;EAC1B,IAAI,CAAC,SAAS;EACd,IAAI;EACJ,IAAI;GACF,UAAU,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC;EACxD,QAAQ;GACN;EACF;EACA,KAAK,MAAM,SAAS,SAClB,WAAW,OAAO,SAAS,QAAQ,KAAK;CAE5C;CAEA,OAAO;AACT;AAEA,SAAS,aAAa,UAAmC;CACvD,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QAAQ,OAAO;CACnB,IAAI;EACF,MAAM,QAAQ,aAAa,UAAU,MAAM,CAAC,CAAC,MAAM,IAAI;EACvD,UAAU,IAAI,UAAU,KAAK;EAC7B,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAUA,SAAS,kBAAkB,OAAgC;CACzD,MAAM,SAAwB,CAAC;CAC/B,IAAI,gBAA+B;CACnC,IAAI,cAAc;CAElB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,QAAQ,sBAAsB,KAAK,MAAM,EAAE;EACjD,IAAI,OAAO;GACT,gBAAgB,MAAM,EAAE,CAAC,YAAY;GACrC,cAAc,IAAI;EACpB;EACA,IAAI,iBAAiB,iBAAiB,KAAK,MAAM,EAAE,GAAG;GACpD,MAAM,UAAU,IAAI;GACpB,OAAO,KAAK;IACV,MAAM;IACN,WAAW;IACX,QAAQ,UAAU,cAAc;GAClC,CAAC;GACD,gBAAgB;EAClB;CACF;CACA,OAAO;AACT;AAEA,SAAS,YACP,SACA,YACA,UACQ;CACR,IAAI,CAAC,SAAS,SAAS,YAAY,GAAG,OAAO;CAE7C,MAAM,QAAQ,aAAa,QAAQ;CACnC,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,SAAS,kBAAkB,KAAK;CACtC,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,IAAI,YAAY;EACd,MAAM,SAAS,OAAO,MACnB,UAAU,MAAM,SAAS,WAAW,YAAY,CACnD;EACA,IAAI,QAAQ,OAAO,OAAO,YAAY,UAAU;CAClD;CAIA,OAAO;AACT;;;;;;;;;AAYA,SAAgB,sBAAsB,UAAU,QAAyB;CACvE,MAAM,aAAa,kBAAkB,OAAO;CAC5C,MAAM,yBAAS,IAAI,IAAoB;CAEvC,IAAI;EACF,IAAI,WAAW,WAAW,UAAU,GAAG;GACrC,MAAM,QAAQ,mBAAmB,WAAW,UAAU,CAAC,CAAC,MACrD,MAAM,UAAU,KAAK,cAAc,KAAK,CAC3C;GAEA,MAAM,4BAAY,IAAI,IAAY;GAClC,KAAK,MAAM,YAAY,OAAO;IAC5B,MAAM,OAAO,SAAS,QAAQ,CAAC,CAAC,YAAY;IAC5C,IAAI,OAAO,IAAI,IAAI,GACjB,UAAU,IAAI,IAAI;SAElB,OAAO,IAAI,MAAM,QAAQ;GAE7B;GACA,KAAK,MAAM,QAAQ,WAAW,OAAO,OAAO,IAAI;EAClD;CACF,QAAQ,CAER;CAEA,IAAI,OAAO,OAAO,GAAG;EACnB,MAAM,WAAW,WAAW;EAC5B,MAAM,SAAS,WAAW,oBAAoB;EAC9C,MAAM,cACJ,UAAU,eAAe,WAAW,yBAAyB;EAC/D,MAAM,iBAAiB,UAAU,kBAAkB;EACnD,QAAQ,IACN,wBAAwB,OAAO,KAAK,yBAAyB,OAAO,gBAAgB,YAAY,mBAAmB,eAAe,EACpI;CACF;CAEA,OAAO,EACL,QACE,YACA,YACA,SACA,YACA,aACkC;EAQlC,MAAM,qBAPgB,cAClB,oBAAoB,WAAW,IAC/B,SAGF,GAAG,WAAW,YAAY,EAAE,GAAG,WAAW,YAAY,EAAE,OAAA,CAClB,MAAM,GACL,CAAC,CAAC,GAAG,EAAE;EAChD,IAAI,CAAC,kBAAkB,OAAO,QAAQ,QAAQ,IAAI;EAClD,MAAM,eAAe,OAAO,IAAI,iBAAiB,YAAY,CAAC;EAE9D,IAAI,CAAC,cAAc,OAAO,QAAQ,QAAQ,IAAI;EAE9C,MAAM,WAAW,YAAY,SAAS,YAAY,YAAY;EAC9D,MAAM,UAAU,QAAQ,SAAS,WAAW,MAAM,YAAY,CAAC;EAC/D,IAAI,CAAC,WAAW,QAAQ,WAAW,KAAK,GAAG,OAAO,QAAQ,QAAQ,IAAI;EAEtE,OAAO,QAAQ,QAAQ;GAAE,MAAM;GAAS,MAAM;EAAS,CAAC;CAC1D,EACF;AACF"}
1
+ {"version":3,"file":"abapgit-D-WCS83I.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(/^\\/+/, '')\n .replace(/(?<!\\/)\\/+$/, '');\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>([^<]*)<\\/FOLDER_LOGIC>/i)?.[1],\n ),\n startingFolder: normalizeStartingFolder(\n xml.match(/<STARTING_FOLDER>([^<]*)<\\/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 const resolvedConfigPaths = new Set<string>();\n for (const value of configPaths) {\n if (/\\.\\./.test(value)) continue;\n resolvedConfigPaths.add(resolvePath(value));\n }\n\n for (const configPath of resolvedConfigPaths) {\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) {\n if (/\\.\\./.test(configuredRoot)) {\n throw new Error(\n 'Repository root must not contain path traversal segments',\n );\n }\n return resolvePath(configuredRoot);\n }\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 = /^\\s*METHOD\\s+(\\w+)/i.exec(lines[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 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.at(-1);\n if (!expectedFilename) return Promise.resolve(null);\n const resolvedPath = lookup.get(expectedFilename.toLowerCase());\n\n if (!resolvedPath) return Promise.resolve(null);\n\n const fileLine = convertLine(atcLine, methodName, resolvedPath);\n const gitPath = toPosix(relative(repository.root, resolvedPath));\n if (!gitPath || gitPath.startsWith('../')) return Promise.resolve(null);\n\n return Promise.resolve({ path: gitPath, line: fileLine });\n },\n };\n}\n"],"mappings":"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"}
@@ -480,7 +480,7 @@ async function loadFindingResolver(options, ctx) {
480
480
  if (!options.resolver) return void 0;
481
481
  const name = options.resolver;
482
482
  if (name === "abapgit") {
483
- const { createAbapGitResolver } = await import("./abapgit-C7MNIMrZ.mjs").then((n) => n.t);
483
+ const { createAbapGitResolver } = await import("./abapgit-D-WCS83I.mjs").then((n) => n.t);
484
484
  return createAbapGitResolver();
485
485
  }
486
486
  try {
@@ -597,4 +597,4 @@ const atcCommand = {
597
597
  };
598
598
  export { enrichResultWithCategories as a, outputGitLabCodeQuality as c, enrichFindingsWithCategories as i, outputSarifReport as l, parseCheckClassFromDocumentation as n, fetchCheckCategoryIndex as o, resolveCategoriesViaDocumentation as r, parseCheckCategoryIndex as s, atcCommand as t };
599
599
 
600
- //# sourceMappingURL=atc-B1R_OPpQ.mjs.map
600
+ //# sourceMappingURL=atc-BOao02if.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"atc-B1R_OPpQ.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/check-categories.ts","../src/documentation-bridge.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\nfunction sarifLevel(priority: number): 'error' | 'warning' | 'note' {\n if (priority === 1) return 'error';\n if (priority === 2) return 'warning';\n return 'note';\n}\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: sarifLevel(f.priority),\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n ...(f.checkCategory && { category: f.checkCategory }),\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: sarifLevel(finding.priority),\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 /\\/methods\\/(\\w+)/i.exec(location) || /[;?&]name=(\\w+)/i.exec(location);\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 // The worklist checkId as SAP reports it. On live systems this is a\n // per-instance GUID (stable for the finding, but not a catalog\n // identity); checkTitle is a display name that SAP can rename, so\n // downstream consumers should key on this field rather than the\n // title.\n check_id: finding.checkId || undefined,\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 ...(finding.checkCategory && {\n check_category: finding.checkCategory,\n }),\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 * Check Category Enrichment\n *\n * Resolves ATC findings to their check category (e.g., 'CI_PERFORMANCE',\n * 'CI_SECURITY') through the check variant form template\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate?chkvName={name}).\n *\n * The form template is the check catalog of the run's check variant: every\n * available check category lists its checks, and each check carries its\n * implementing class. When a finding's checkId matches a catalog\n * implementing class, the index resolves it directly. Live worklist\n * checkIds are check-instance GUIDs (observed on a live S/4 system,\n * 2026-10-22); those resolve through the documentation bridge in\n * ./documentation-bridge.ts.\n *\n * Enrichment is best-effort by design: without the form template, or for a\n * checkId outside the catalog, findings keep no category and the run still\n * succeeds.\n */\n\n/**\n * Structural shape of the parsed check variant form template document\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate).\n *\n * Only the fields needed for the checkId -> category index are declared.\n */\nexport interface FormTemplateDocument {\n formTemplate?: {\n availableCheckCategories?: {\n availableCheckCategory?: FormTemplateCategoryEntry[];\n };\n };\n}\n\n/** One available check category with the checks it contains. */\nexport interface FormTemplateCategoryEntry {\n checkCategoryObject?: {\n /** Category name (the enrichment key, e.g., 'CI_PERFORMANCE') */\n name?: string;\n /** Category display title (e.g., 'Performance') */\n description?: string;\n };\n check?: FormTemplateCheckEntry[];\n}\n\n/** One check entry of a category. */\nexport interface FormTemplateCheckEntry {\n checkObject?: {\n /** Check name (e.g., 'CI_ANALYZE_WHERE') */\n name?: string;\n content?: {\n /** The check's category assignment (the category name) */\n category?: { name?: string };\n /**\n * Implementing class of the check (e.g., 'CL_CI_TEST_SELECT_TAW_A').\n * Worklist findings carry a check-instance GUID instead; the\n * documentation bridge resolves that GUID to this class name.\n */\n implementingClass?: { name?: string };\n };\n };\n}\n\n/**\n * Build a checkId -> check category name index from a parsed check variant\n * form template document.\n *\n * The checkId key is the implementing class name; the value is the category\n * object name (a stable key like 'CI_PERFORMANCE', not the display title).\n *\n * @param doc - Parsed form template document (nullable for convenience)\n * @returns Map of checkId -> category name; empty when the document is\n * missing or malformed\n */\nexport function parseCheckCategoryIndex(\n doc: FormTemplateDocument | null | undefined,\n): Map<string, string> {\n const index = new Map<string, string>();\n const categories =\n doc?.formTemplate?.availableCheckCategories?.availableCheckCategory ?? [];\n for (const category of categories) {\n collectCategoryChecks(category, index);\n }\n return index;\n}\n\n/** Index every check of one category under the category's name. */\nfunction collectCategoryChecks(\n category: FormTemplateCategoryEntry,\n index: Map<string, string>,\n): void {\n const categoryName = category.checkCategoryObject?.name;\n if (!categoryName) return;\n const checkIds = (category.check ?? [])\n .map((check) => check.checkObject?.content?.implementingClass?.name)\n .filter((checkId): checkId is string => !!checkId);\n for (const checkId of checkIds) {\n index.set(checkId, categoryName);\n }\n}\n\n/** Structural client surface needed to fetch a form template. */\nexport interface FormTemplateClient {\n adt: {\n atc: {\n checkvariants: {\n /** GET /sap/bc/adt/atc/checkvariants/formtemplate{?chkvName} */\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<FormTemplateDocument>;\n };\n };\n };\n}\n\n/**\n * Fetch the check variant form template and build the checkId -> category\n * name index for a run.\n *\n * Best-effort: any error (network, 404, malformed document) is swallowed\n * and reported through the optional logger so a failed enrichment never\n * fails the ATC run itself.\n *\n * @param client - ADT client with the checkvariants.formtemplate surface\n * @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')\n * @param logger - Optional CLI logger for diagnostics\n * @returns checkId -> category name index; empty when unavailable\n */\nexport async function fetchCheckCategoryIndex(\n client: FormTemplateClient,\n checkVariant: string,\n logger?: {\n warn: (message: string) => void;\n info?: (message: string) => void;\n },\n): Promise<Map<string, string>> {\n try {\n const doc = await client.adt.atc.checkvariants.formtemplate({\n chkvName: checkVariant,\n });\n return parseCheckCategoryIndex(doc);\n } catch (error) {\n logger?.warn?.(\n `⚠️ Check category enrichment unavailable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return new Map();\n }\n}\n\n/**\n * Enrich findings with their check category in place.\n *\n * Generic over any finding shape carrying a checkId (and an optional\n * checkCategory field), so both the CLI plugin's AtcFinding and other\n * canonical finding types can be enriched with the same index.\n *\n * @param findings - Findings to enrich (mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichFindingsWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(findings: T[], index: Map<string, string>): number {\n let enriched = 0;\n for (const finding of findings) {\n const checkCategory = index.get(finding.checkId);\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\n}\n\n/**\n * Enrich an ATC result with check categories in place.\n *\n * @param result - The run result (findings mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichResultWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(result: { findings?: T[] }, index: Map<string, string>): number {\n return enrichFindingsWithCategories(result.findings ?? [], index);\n}\n","/**\n * Documentation Bridge for Check Category Enrichment\n *\n * ATC worklist findings carry a check-instance GUID as their checkId — not\n * the implementing class name the check variant form template catalog lists\n * (observed on a live S/4 system, 2026-10-22). The finding's documentation\n * page (the worklist atom:link) names the check class in its header line,\n * which resolves through the form-template category index.\n *\n * Everything here is best-effort: fetch errors are reported through the\n * optional logger and skipped; findings keep no category when their page\n * cannot be fetched or parsed.\n */\n\n/** Structural surface for a bounded documentation read. The real ADT client\n * provides readTextBounded; mocks without it simply skip the bridge. */\nexport interface DocumentationReadingClient {\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\n}\n\n/**\n * Parse the implementing check class from an ATC finding documentation page.\n *\n * Live shape (2026-10-22): the page opens with\n * \"Appl. Comp. Check / Check Class / Message Code\" followed by\n * `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in\n * that line is the implementing class the check variant form template lists\n * — the bridge from the finding's GUID checkId to the check catalog.\n *\n * The match is bounded (no unbounded quantifiers on page input) and accepts\n * an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).\n *\n * @param html - The documentation page HTML\n * @returns The check class name, or undefined when the page does not carry it\n */\nexport function parseCheckClassFromDocumentation(\n html: string,\n): string | undefined {\n const match =\n /Check\\s*Class[^<]{0,80}<\\/h3>\\s*<p>\\s*[^/<]{0,120}\\/\\s*((?:\\/\\w{1,8}\\/)?[A-Za-z]\\w*)\\s*\\//u.exec(\n html,\n );\n return match?.[1];\n}\n\n/** Finding shape the bridge enriches in place. */\nexport interface DocumentationBridgeFinding {\n checkId: string;\n checkCategory?: string;\n documentationUri?: string;\n}\n\n/**\n * Only ATC documentation paths may be fetched — a worklist-provided URI\n * must never drive an authenticated read outside the documentation space.\n */\nconst ATC_DOCUMENTATION_URI =\n /^\\/sap\\/bc\\/adt\\/documentation\\/atc\\/documents\\/[\\w/%-]+$/u;\n\n/** Shared state for the per-URI documentation fetch. */\ninterface DocumentationFetchContext {\n read: NonNullable<DocumentationReadingClient['readTextBounded']>;\n maxBytes: number;\n maxReads: number;\n reads: number;\n classByDocumentationUri: Map<string, string | undefined>;\n logger?: { warn: (message: string) => void };\n}\n\n/**\n * Read one documentation page and resolve its check class, with a shared\n * per-URI cache so repeated findings never re-fetch the same page.\n */\nasync function resolveCheckClassForUri(\n documentationUri: string,\n context: DocumentationFetchContext,\n): Promise<void> {\n if (context.classByDocumentationUri.has(documentationUri)) return;\n if (!ATC_DOCUMENTATION_URI.test(documentationUri)) {\n context.logger?.warn?.(\n `⚠️ Skipping non-ATC documentation URI: ${documentationUri}`,\n );\n context.classByDocumentationUri.set(documentationUri, undefined);\n return;\n }\n if (context.reads >= context.maxReads) {\n context.classByDocumentationUri.set(documentationUri, undefined);\n return;\n }\n context.reads++;\n try {\n const html = await context.read(documentationUri, context.maxBytes, {\n headers: { Accept: 'application/vnd.sap.adt.docu.v1+html' },\n });\n context.classByDocumentationUri.set(\n documentationUri,\n parseCheckClassFromDocumentation(html),\n );\n } catch (error) {\n context.logger?.warn?.(\n `⚠️ ATC documentation read failed for ${documentationUri}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n context.classByDocumentationUri.set(documentationUri, undefined);\n }\n}\n\n/**\n * Bridge finding categories through the ATC documentation page.\n *\n * After the direct form-template index pass (which resolves findings whose\n * checkId already matches a catalog implementing class), this resolves the\n * GUID-checkId findings: each finding's documentation page names the check\n * class, which resolves through the same index.\n *\n * Best-effort: per-documentation-URI fetch errors are reported through the\n * optional logger and skipped; a finding keeps no category when its page\n * cannot be fetched or parsed. Reads are deduplicated per URI.\n *\n * @param client - ADT client with an optional bounded text read\n * @param findings - Findings to enrich in place\n * @param classToCategory - checkId/class -> category name index (the\n * form-template catalog: parseCheckCategoryIndex output)\n * @param options - maxBytes (default 64 KiB) and an optional logger\n * @returns Number of findings that received a category\n */\nexport async function resolveCategoriesViaDocumentation(\n client: DocumentationReadingClient,\n findings: DocumentationBridgeFinding[],\n classToCategory: ReadonlyMap<string, string>,\n options: {\n maxBytes?: number;\n maxReads?: number;\n logger?: { warn: (message: string) => void };\n } = {},\n): Promise<number> {\n const read = client.readTextBounded;\n if (!read || classToCategory.size === 0) return 0;\n\n const pending = findings.filter(\n (finding) => !finding.checkCategory && finding.documentationUri,\n );\n if (pending.length === 0) return 0;\n\n const context: DocumentationFetchContext = {\n read,\n maxBytes: options.maxBytes ?? 64 * 1024,\n maxReads: options.maxReads ?? 500,\n reads: 0,\n classByDocumentationUri: new Map<string, string | undefined>(),\n logger: options.logger,\n };\n let enriched = 0;\n for (const finding of pending) {\n await resolveCheckClassForUri(finding.documentationUri!, context);\n const checkClass = context.classByDocumentationUri.get(\n finding.documentationUri!,\n );\n const checkCategory = checkClass\n ? classToCategory.get(checkClass)\n : undefined;\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\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 {\n fetchCheckCategoryIndex,\n enrichResultWithCategories,\n} from '../check-categories';\nimport { resolveCategoriesViaDocumentation } from '../documentation-bridge';\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 checkvariants: {\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<import('../check-categories').FormTemplateDocument>;\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 * Bounded documentation text read (the real ADT client provides it).\n * Optional: without it the documentation category bridge is skipped.\n */\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\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 /** Documentation link: the page that names the check class. */\n link?: Array<{ href?: string; rel?: 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 * The trusted documentation link of one worklist finding.\n *\n * Live shape (2026-10-22): <atom:link rel=\"http://www.sap.com/adt/\n * relations/documentation\" href=\"/sap/bc/adt/documentation/atc/documents/\n * itemid/{guid}/index/{n}\"/> inside the atcfinding:finding element.\n */\nfunction documentationUriFromFinding(\n finding: NonNullable<\n NonNullable<\n NonNullable<WorklistResponse['worklist']>['objects']['object']\n >[number]['findings']['finding']\n >[number],\n): string | undefined {\n return finding.link?.find(\n (link) => link.rel === 'http://www.sap.com/adt/relations/documentation',\n )?.href;\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 documentationUri: documentationUriFromFinding(finding),\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 let priorityIcon = 'ℹ️';\n if (finding.priority === 1) priorityIcon = '❌';\n else if (finding.priority === 2) priorityIcon = '⚠️';\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 */\n/** CLI options parsed for the `atc` command. */\ninterface AtcCliOptions {\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/** Resolve the single ATC target from the mutually exclusive options. */\nasync function resolveTargets(\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('node:fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n const 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 const targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n return { targetUris, targetName };\n }\n if (options.transport) {\n const targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n return {\n targetUris: [`/sap/bc/adt/cts/transportrequests/${options.transport}`],\n targetName,\n };\n }\n if (options.package) {\n const targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName,\n };\n }\n const targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n return { targetUris: [options.object!], targetName };\n}\n\n/**\n * Determine the check variant: the explicit `--variant`, the system\n * default from ATC customizing, or `DEFAULT`.\n */\nasync function resolveCheckVariant(\n client: AdtClient,\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<string> {\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 if (options.variant) return options.variant;\n\n // Find systemCheckVariant in properties\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n return variantProp?.value ? variantProp.value : 'DEFAULT';\n}\n\n/** Extract the worklist ID — SAP can return a plain string or an object. */\nfunction extractWorklistId(\n worklistCreateResponse:\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } },\n ctx: CliContext,\n): string {\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = /id=\"([^\"]+)\"/.exec(worklistCreateResponse);\n return idMatch ? idMatch[1] : worklistCreateResponse.trim();\n }\n if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n return worklistCreateResponse.worklist.id;\n }\n if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n return worklistCreateResponse.worklistRun.worklistId;\n }\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n}\n\n/** Load the finding resolver plugin when requested. */\nasync function loadFindingResolver(\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<import('../types').FindingResolver | undefined> {\n if (!options.resolver) return undefined;\n\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 return createAbapGitResolver();\n }\n\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 return mod.createFindingResolver();\n }\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n return undefined;\n}\n\n/** Output the ATC results in the requested format. */\nasync function outputAtcResults(\n result: AtcResult,\n options: AtcCliOptions,\n targetName: string,\n resolver: import('../types').FindingResolver | undefined,\n ctx: CliContext,\n): Promise<void> {\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\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 AtcCliOptions;\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 const { targetUris, targetName } = await resolveTargets(options, ctx);\n\n // Determine the check variant\n const checkVariant = await resolveCheckVariant(client, options, ctx);\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 const worklistId = extractWorklistId(worklistCreateResponse, ctx);\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 // Enrich findings with check categories from the run's check variant\n // form template (best-effort: never fails the run itself). The catalog is\n // fetched after the run, so categories reflect the variant configuration\n // in effect at that time.\n const categoryIndex = await fetchCheckCategoryIndex(\n client,\n checkVariant,\n ctx.logger,\n );\n const enriched = enrichResultWithCategories(result, categoryIndex);\n if (enriched > 0) {\n ctx.logger.info(\n `🏷️ Enriched ${enriched}/${result.totalFindings} finding(s) with check categories`,\n );\n }\n\n // Bridge remaining findings through their ATC documentation page: the\n // worklist checkId is a check-instance GUID, not the implementing class\n // the form-template catalog lists (live evidence, 2026-10-22), so\n // the direct index match cannot resolve them. The documentation page\n // names the check class, which resolves through the same index.\n // Best-effort: never fails the run.\n const bridged = await resolveCategoriesViaDocumentation(\n client,\n result.findings,\n categoryIndex,\n { logger: ctx.logger },\n );\n if (bridged > 0) {\n ctx.logger.info(\n `🏷 Bridged ${bridged} finding(s) to check categories via ATC documentation`,\n );\n }\n\n // Load finding resolver if requested\n const resolver = await loadFindingResolver(options, ctx);\n\n // Output based on format\n await outputAtcResults(result, options, targetName, resolver, ctx);\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
+ {"version":3,"file":"atc-BOao02if.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/check-categories.ts","../src/documentation-bridge.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\nfunction sarifLevel(priority: number): 'error' | 'warning' | 'note' {\n if (priority === 1) return 'error';\n if (priority === 2) return 'warning';\n return 'note';\n}\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: sarifLevel(f.priority),\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n ...(f.checkCategory && { category: f.checkCategory }),\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: sarifLevel(finding.priority),\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 /\\/methods\\/(\\w+)/i.exec(location) || /[;?&]name=(\\w+)/i.exec(location);\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 // The worklist checkId as SAP reports it. On live systems this is a\n // per-instance GUID (stable for the finding, but not a catalog\n // identity); checkTitle is a display name that SAP can rename, so\n // downstream consumers should key on this field rather than the\n // title.\n check_id: finding.checkId || undefined,\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 ...(finding.checkCategory && {\n check_category: finding.checkCategory,\n }),\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 * Check Category Enrichment\n *\n * Resolves ATC findings to their check category (e.g., 'CI_PERFORMANCE',\n * 'CI_SECURITY') through the check variant form template\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate?chkvName={name}).\n *\n * The form template is the check catalog of the run's check variant: every\n * available check category lists its checks, and each check carries its\n * implementing class. When a finding's checkId matches a catalog\n * implementing class, the index resolves it directly. Live worklist\n * checkIds are check-instance GUIDs (observed on a live S/4 system,\n * 2026-10-22); those resolve through the documentation bridge in\n * ./documentation-bridge.ts.\n *\n * Enrichment is best-effort by design: without the form template, or for a\n * checkId outside the catalog, findings keep no category and the run still\n * succeeds.\n */\n\n/**\n * Structural shape of the parsed check variant form template document\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate).\n *\n * Only the fields needed for the checkId -> category index are declared.\n */\nexport interface FormTemplateDocument {\n formTemplate?: {\n availableCheckCategories?: {\n availableCheckCategory?: FormTemplateCategoryEntry[];\n };\n };\n}\n\n/** One available check category with the checks it contains. */\nexport interface FormTemplateCategoryEntry {\n checkCategoryObject?: {\n /** Category name (the enrichment key, e.g., 'CI_PERFORMANCE') */\n name?: string;\n /** Category display title (e.g., 'Performance') */\n description?: string;\n };\n check?: FormTemplateCheckEntry[];\n}\n\n/** One check entry of a category. */\nexport interface FormTemplateCheckEntry {\n checkObject?: {\n /** Check name (e.g., 'CI_ANALYZE_WHERE') */\n name?: string;\n content?: {\n /** The check's category assignment (the category name) */\n category?: { name?: string };\n /**\n * Implementing class of the check (e.g., 'CL_CI_TEST_SELECT_TAW_A').\n * Worklist findings carry a check-instance GUID instead; the\n * documentation bridge resolves that GUID to this class name.\n */\n implementingClass?: { name?: string };\n };\n };\n}\n\n/**\n * Build a checkId -> check category name index from a parsed check variant\n * form template document.\n *\n * The checkId key is the implementing class name; the value is the category\n * object name (a stable key like 'CI_PERFORMANCE', not the display title).\n *\n * @param doc - Parsed form template document (nullable for convenience)\n * @returns Map of checkId -> category name; empty when the document is\n * missing or malformed\n */\nexport function parseCheckCategoryIndex(\n doc: FormTemplateDocument | null | undefined,\n): Map<string, string> {\n const index = new Map<string, string>();\n const categories =\n doc?.formTemplate?.availableCheckCategories?.availableCheckCategory ?? [];\n for (const category of categories) {\n collectCategoryChecks(category, index);\n }\n return index;\n}\n\n/** Index every check of one category under the category's name. */\nfunction collectCategoryChecks(\n category: FormTemplateCategoryEntry,\n index: Map<string, string>,\n): void {\n const categoryName = category.checkCategoryObject?.name;\n if (!categoryName) return;\n const checkIds = (category.check ?? [])\n .map((check) => check.checkObject?.content?.implementingClass?.name)\n .filter((checkId): checkId is string => !!checkId);\n for (const checkId of checkIds) {\n index.set(checkId, categoryName);\n }\n}\n\n/** Structural client surface needed to fetch a form template. */\nexport interface FormTemplateClient {\n adt: {\n atc: {\n checkvariants: {\n /** GET /sap/bc/adt/atc/checkvariants/formtemplate{?chkvName} */\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<FormTemplateDocument>;\n };\n };\n };\n}\n\n/**\n * Fetch the check variant form template and build the checkId -> category\n * name index for a run.\n *\n * Best-effort: any error (network, 404, malformed document) is swallowed\n * and reported through the optional logger so a failed enrichment never\n * fails the ATC run itself.\n *\n * @param client - ADT client with the checkvariants.formtemplate surface\n * @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')\n * @param logger - Optional CLI logger for diagnostics\n * @returns checkId -> category name index; empty when unavailable\n */\nexport async function fetchCheckCategoryIndex(\n client: FormTemplateClient,\n checkVariant: string,\n logger?: {\n warn: (message: string) => void;\n info?: (message: string) => void;\n },\n): Promise<Map<string, string>> {\n try {\n const doc = await client.adt.atc.checkvariants.formtemplate({\n chkvName: checkVariant,\n });\n return parseCheckCategoryIndex(doc);\n } catch (error) {\n logger?.warn?.(\n `⚠️ Check category enrichment unavailable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return new Map();\n }\n}\n\n/**\n * Enrich findings with their check category in place.\n *\n * Generic over any finding shape carrying a checkId (and an optional\n * checkCategory field), so both the CLI plugin's AtcFinding and other\n * canonical finding types can be enriched with the same index.\n *\n * @param findings - Findings to enrich (mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichFindingsWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(findings: T[], index: Map<string, string>): number {\n let enriched = 0;\n for (const finding of findings) {\n const checkCategory = index.get(finding.checkId);\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\n}\n\n/**\n * Enrich an ATC result with check categories in place.\n *\n * @param result - The run result (findings mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichResultWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(result: { findings?: T[] }, index: Map<string, string>): number {\n return enrichFindingsWithCategories(result.findings ?? [], index);\n}\n","/**\n * Documentation Bridge for Check Category Enrichment\n *\n * ATC worklist findings carry a check-instance GUID as their checkId — not\n * the implementing class name the check variant form template catalog lists\n * (observed on a live S/4 system, 2026-10-22). The finding's documentation\n * page (the worklist atom:link) names the check class in its header line,\n * which resolves through the form-template category index.\n *\n * Everything here is best-effort: fetch errors are reported through the\n * optional logger and skipped; findings keep no category when their page\n * cannot be fetched or parsed.\n */\n\n/** Structural surface for a bounded documentation read. The real ADT client\n * provides readTextBounded; mocks without it simply skip the bridge. */\nexport interface DocumentationReadingClient {\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\n}\n\n/**\n * Parse the implementing check class from an ATC finding documentation page.\n *\n * Live shape (2026-10-22): the page opens with\n * \"Appl. Comp. Check / Check Class / Message Code\" followed by\n * `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in\n * that line is the implementing class the check variant form template lists\n * — the bridge from the finding's GUID checkId to the check catalog.\n *\n * The match is bounded (no unbounded quantifiers on page input) and accepts\n * an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).\n *\n * @param html - The documentation page HTML\n * @returns The check class name, or undefined when the page does not carry it\n */\nexport function parseCheckClassFromDocumentation(\n html: string,\n): string | undefined {\n const match =\n /Check\\s*Class[^<]{0,80}<\\/h3>\\s*<p>\\s*[^/<]{0,120}\\/\\s*((?:\\/\\w{1,8}\\/)?[A-Za-z]\\w*)\\s*\\//u.exec(\n html,\n );\n return match?.[1];\n}\n\n/** Finding shape the bridge enriches in place. */\nexport interface DocumentationBridgeFinding {\n checkId: string;\n checkCategory?: string;\n documentationUri?: string;\n}\n\n/**\n * Only ATC documentation paths may be fetched — a worklist-provided URI\n * must never drive an authenticated read outside the documentation space.\n */\nconst ATC_DOCUMENTATION_URI =\n /^\\/sap\\/bc\\/adt\\/documentation\\/atc\\/documents\\/[\\w/%-]+$/u;\n\n/** Shared state for the per-URI documentation fetch. */\ninterface DocumentationFetchContext {\n read: NonNullable<DocumentationReadingClient['readTextBounded']>;\n maxBytes: number;\n maxReads: number;\n reads: number;\n classByDocumentationUri: Map<string, string | undefined>;\n logger?: { warn: (message: string) => void };\n}\n\n/**\n * Read one documentation page and resolve its check class, with a shared\n * per-URI cache so repeated findings never re-fetch the same page.\n */\nasync function resolveCheckClassForUri(\n documentationUri: string,\n context: DocumentationFetchContext,\n): Promise<void> {\n if (context.classByDocumentationUri.has(documentationUri)) return;\n if (!ATC_DOCUMENTATION_URI.test(documentationUri)) {\n context.logger?.warn?.(\n `⚠️ Skipping non-ATC documentation URI: ${documentationUri}`,\n );\n context.classByDocumentationUri.set(documentationUri, undefined);\n return;\n }\n if (context.reads >= context.maxReads) {\n context.classByDocumentationUri.set(documentationUri, undefined);\n return;\n }\n context.reads++;\n try {\n const html = await context.read(documentationUri, context.maxBytes, {\n headers: { Accept: 'application/vnd.sap.adt.docu.v1+html' },\n });\n context.classByDocumentationUri.set(\n documentationUri,\n parseCheckClassFromDocumentation(html),\n );\n } catch (error) {\n context.logger?.warn?.(\n `⚠️ ATC documentation read failed for ${documentationUri}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n context.classByDocumentationUri.set(documentationUri, undefined);\n }\n}\n\n/**\n * Bridge finding categories through the ATC documentation page.\n *\n * After the direct form-template index pass (which resolves findings whose\n * checkId already matches a catalog implementing class), this resolves the\n * GUID-checkId findings: each finding's documentation page names the check\n * class, which resolves through the same index.\n *\n * Best-effort: per-documentation-URI fetch errors are reported through the\n * optional logger and skipped; a finding keeps no category when its page\n * cannot be fetched or parsed. Reads are deduplicated per URI.\n *\n * @param client - ADT client with an optional bounded text read\n * @param findings - Findings to enrich in place\n * @param classToCategory - checkId/class -> category name index (the\n * form-template catalog: parseCheckCategoryIndex output)\n * @param options - maxBytes (default 64 KiB) and an optional logger\n * @returns Number of findings that received a category\n */\nexport async function resolveCategoriesViaDocumentation(\n client: DocumentationReadingClient,\n findings: DocumentationBridgeFinding[],\n classToCategory: ReadonlyMap<string, string>,\n options: {\n maxBytes?: number;\n maxReads?: number;\n logger?: { warn: (message: string) => void };\n } = {},\n): Promise<number> {\n const read = client.readTextBounded;\n if (!read || classToCategory.size === 0) return 0;\n\n const pending = findings.filter(\n (finding) => !finding.checkCategory && finding.documentationUri,\n );\n if (pending.length === 0) return 0;\n\n const context: DocumentationFetchContext = {\n read,\n maxBytes: options.maxBytes ?? 64 * 1024,\n maxReads: options.maxReads ?? 500,\n reads: 0,\n classByDocumentationUri: new Map<string, string | undefined>(),\n logger: options.logger,\n };\n let enriched = 0;\n for (const finding of pending) {\n await resolveCheckClassForUri(finding.documentationUri!, context);\n const checkClass = context.classByDocumentationUri.get(\n finding.documentationUri!,\n );\n const checkCategory = checkClass\n ? classToCategory.get(checkClass)\n : undefined;\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\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 {\n fetchCheckCategoryIndex,\n enrichResultWithCategories,\n} from '../check-categories';\nimport { resolveCategoriesViaDocumentation } from '../documentation-bridge';\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 checkvariants: {\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<import('../check-categories').FormTemplateDocument>;\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 * Bounded documentation text read (the real ADT client provides it).\n * Optional: without it the documentation category bridge is skipped.\n */\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\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 /** Documentation link: the page that names the check class. */\n link?: Array<{ href?: string; rel?: 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 * The trusted documentation link of one worklist finding.\n *\n * Live shape (2026-10-22): <atom:link rel=\"http://www.sap.com/adt/\n * relations/documentation\" href=\"/sap/bc/adt/documentation/atc/documents/\n * itemid/{guid}/index/{n}\"/> inside the atcfinding:finding element.\n */\nfunction documentationUriFromFinding(\n finding: NonNullable<\n NonNullable<\n NonNullable<WorklistResponse['worklist']>['objects']['object']\n >[number]['findings']['finding']\n >[number],\n): string | undefined {\n return finding.link?.find(\n (link) => link.rel === 'http://www.sap.com/adt/relations/documentation',\n )?.href;\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 documentationUri: documentationUriFromFinding(finding),\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 let priorityIcon = 'ℹ️';\n if (finding.priority === 1) priorityIcon = '❌';\n else if (finding.priority === 2) priorityIcon = '⚠️';\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 */\n/** CLI options parsed for the `atc` command. */\ninterface AtcCliOptions {\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/** Resolve the single ATC target from the mutually exclusive options. */\nasync function resolveTargets(\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('node:fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n const 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 const targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n return { targetUris, targetName };\n }\n if (options.transport) {\n const targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n return {\n targetUris: [`/sap/bc/adt/cts/transportrequests/${options.transport}`],\n targetName,\n };\n }\n if (options.package) {\n const targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName,\n };\n }\n const targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n return { targetUris: [options.object!], targetName };\n}\n\n/**\n * Determine the check variant: the explicit `--variant`, the system\n * default from ATC customizing, or `DEFAULT`.\n */\nasync function resolveCheckVariant(\n client: AdtClient,\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<string> {\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 if (options.variant) return options.variant;\n\n // Find systemCheckVariant in properties\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n return variantProp?.value ? variantProp.value : 'DEFAULT';\n}\n\n/** Extract the worklist ID — SAP can return a plain string or an object. */\nfunction extractWorklistId(\n worklistCreateResponse:\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } },\n ctx: CliContext,\n): string {\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = /id=\"([^\"]+)\"/.exec(worklistCreateResponse);\n return idMatch ? idMatch[1] : worklistCreateResponse.trim();\n }\n if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n return worklistCreateResponse.worklist.id;\n }\n if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n return worklistCreateResponse.worklistRun.worklistId;\n }\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n}\n\n/** Load the finding resolver plugin when requested. */\nasync function loadFindingResolver(\n options: AtcCliOptions,\n ctx: CliContext,\n): Promise<import('../types').FindingResolver | undefined> {\n if (!options.resolver) return undefined;\n\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 return createAbapGitResolver();\n }\n\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 return mod.createFindingResolver();\n }\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n return undefined;\n}\n\n/** Output the ATC results in the requested format. */\nasync function outputAtcResults(\n result: AtcResult,\n options: AtcCliOptions,\n targetName: string,\n resolver: import('../types').FindingResolver | undefined,\n ctx: CliContext,\n): Promise<void> {\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\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 AtcCliOptions;\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 const { targetUris, targetName } = await resolveTargets(options, ctx);\n\n // Determine the check variant\n const checkVariant = await resolveCheckVariant(client, options, ctx);\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 const worklistId = extractWorklistId(worklistCreateResponse, ctx);\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 // Enrich findings with check categories from the run's check variant\n // form template (best-effort: never fails the run itself). The catalog is\n // fetched after the run, so categories reflect the variant configuration\n // in effect at that time.\n const categoryIndex = await fetchCheckCategoryIndex(\n client,\n checkVariant,\n ctx.logger,\n );\n const enriched = enrichResultWithCategories(result, categoryIndex);\n if (enriched > 0) {\n ctx.logger.info(\n `🏷️ Enriched ${enriched}/${result.totalFindings} finding(s) with check categories`,\n );\n }\n\n // Bridge remaining findings through their ATC documentation page: the\n // worklist checkId is a check-instance GUID, not the implementing class\n // the form-template catalog lists (live evidence, 2026-10-22), so\n // the direct index match cannot resolve them. The documentation page\n // names the check class, which resolves through the same index.\n // Best-effort: never fails the run.\n const bridged = await resolveCategoriesViaDocumentation(\n client,\n result.findings,\n categoryIndex,\n { logger: ctx.logger },\n );\n if (bridged > 0) {\n ctx.logger.info(\n `🏷 Bridged ${bridged} finding(s) to check categories via ATC documentation`,\n );\n }\n\n // Load finding resolver if requested\n const resolver = await loadFindingResolver(options, ctx);\n\n // Output based on format\n await outputAtcResults(result, options, targetName, resolver, ctx);\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 +1 @@
1
- export { t as atcCommand, t as default } from "../atc-B1R_OPpQ.mjs";
1
+ export { t as atcCommand, t as default } from "../atc-BOao02if.mjs";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as createAbapGitResolver, r as adtUriToAbapGitPath } from "./abapgit-C7MNIMrZ.mjs";
2
- import { a as enrichResultWithCategories, c as outputGitLabCodeQuality, i as enrichFindingsWithCategories, l as outputSarifReport, n as parseCheckClassFromDocumentation, o as fetchCheckCategoryIndex, r as resolveCategoriesViaDocumentation, s as parseCheckCategoryIndex, t as atcCommand } from "./atc-B1R_OPpQ.mjs";
1
+ import { n as createAbapGitResolver, r as adtUriToAbapGitPath } from "./abapgit-D-WCS83I.mjs";
2
+ import { a as enrichResultWithCategories, c as outputGitLabCodeQuality, i as enrichFindingsWithCategories, l as outputSarifReport, n as parseCheckClassFromDocumentation, o as fetchCheckCategoryIndex, r as resolveCategoriesViaDocumentation, s as parseCheckCategoryIndex, t as atcCommand } from "./atc-BOao02if.mjs";
3
3
  // src/commands/atc-customizing.ts
4
4
  const atcCustomizingCommand = {
5
5
  name: "atc-customizing",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-atc",
3
- "version": "0.4.44",
3
+ "version": "0.4.46",
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.44",
31
+ "@abapify/adt-plugin": "0.4.46",
32
32
  "chalk": "^5.3.0"
33
33
  },
34
34
  "module": "./dist/index.mjs",