@abapify/adt-atc 0.4.19 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/{atc-BEoE4xIY.mjs → atc-6cFtFkir.mjs} +177 -5
- package/dist/atc-6cFtFkir.mjs.map +1 -0
- package/dist/commands/atc.d.mts.map +1 -1
- package/dist/commands/atc.mjs +1 -1
- package/dist/index.d.mts +215 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
- package/dist/atc-BEoE4xIY.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -82,6 +82,9 @@ const result: AtcResult = {
|
|
|
82
82
|
objectType: 'CLAS',
|
|
83
83
|
objectName: 'ZCL_MY_CLASS',
|
|
84
84
|
location: '/sap/bc/adt/oo/classes/zcl_my_class/source/main#start=10,0',
|
|
85
|
+
// Optional: resolved by the atc command from the run's check variant
|
|
86
|
+
// form template (check categories are best-effort enrichment)
|
|
87
|
+
checkCategory: 'CI_PERFORMANCE',
|
|
85
88
|
},
|
|
86
89
|
],
|
|
87
90
|
};
|
|
@@ -90,6 +93,40 @@ await outputSarifReport(result, 'atc-results.sarif');
|
|
|
90
93
|
await outputGitLabCodeQuality(result, 'gl-code-quality.json');
|
|
91
94
|
```
|
|
92
95
|
|
|
96
|
+
## Check Categories
|
|
97
|
+
|
|
98
|
+
Each finding can carry a `checkCategory` (e.g., `CI_PERFORMANCE`,
|
|
99
|
+
`CI_SECURITY`): the name of the ATC check category its check is assigned
|
|
100
|
+
to. The `atc` command resolves it automatically from the run's check
|
|
101
|
+
variant form template (`GET /sap/bc/adt/atc/checkvariants/formtemplate`)
|
|
102
|
+
by mapping the finding's `checkId` (the check's implementing class name)
|
|
103
|
+
to the category that contains it. The enrichment is best-effort: when
|
|
104
|
+
the form template is unavailable, or the check is outside its catalog,
|
|
105
|
+
findings simply carry no category and the run still succeeds.
|
|
106
|
+
|
|
107
|
+
The enrichment helpers are exported for reuse:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import {
|
|
111
|
+
parseCheckCategoryIndex,
|
|
112
|
+
fetchCheckCategoryIndex,
|
|
113
|
+
enrichResultWithCategories,
|
|
114
|
+
} from '@abapify/adt-atc';
|
|
115
|
+
|
|
116
|
+
// Build the checkId -> category name index from a parsed form template
|
|
117
|
+
const index = parseCheckCategoryIndex(formTemplateDocument);
|
|
118
|
+
|
|
119
|
+
// Or fetch it through an ADT client (never throws)
|
|
120
|
+
const fetched = await fetchCheckCategoryIndex(client, 'DEFAULT');
|
|
121
|
+
|
|
122
|
+
// Enrich a result in place
|
|
123
|
+
enrichResultWithCategories(result, index);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The GitLab Code Quality formatter emits the category as `check_category`
|
|
127
|
+
per finding; the SARIF formatter sets it on each rule's
|
|
128
|
+
`properties.category`.
|
|
129
|
+
|
|
93
130
|
## Output Formats
|
|
94
131
|
|
|
95
132
|
### Console (default)
|
|
@@ -15,7 +15,8 @@ async function outputSarifReport(result, outputFile, _targetName) {
|
|
|
15
15
|
defaultConfiguration: { level: f.priority === 1 ? "error" : f.priority === 2 ? "warning" : "note" },
|
|
16
16
|
properties: {
|
|
17
17
|
tags: ["abap", "code-quality"],
|
|
18
|
-
precision: "high"
|
|
18
|
+
precision: "high",
|
|
19
|
+
...f.checkCategory && { category: f.checkCategory }
|
|
19
20
|
}
|
|
20
21
|
}])).values());
|
|
21
22
|
const results = result.findings.map((finding) => {
|
|
@@ -125,6 +126,7 @@ async function outputGitLabCodeQuality(result, outputFile, options) {
|
|
|
125
126
|
return {
|
|
126
127
|
description: finding.messageText,
|
|
127
128
|
check_name: finding.checkTitle || finding.checkId,
|
|
129
|
+
check_id: finding.checkId || void 0,
|
|
128
130
|
fingerprint,
|
|
129
131
|
severity,
|
|
130
132
|
location: {
|
|
@@ -135,13 +137,167 @@ async function outputGitLabCodeQuality(result, outputFile, options) {
|
|
|
135
137
|
}
|
|
136
138
|
},
|
|
137
139
|
...methodName && { method: methodName },
|
|
138
|
-
...finding.location && { atc_location: finding.location }
|
|
140
|
+
...finding.location && { atc_location: finding.location },
|
|
141
|
+
...finding.checkCategory && { check_category: finding.checkCategory }
|
|
139
142
|
};
|
|
140
143
|
}));
|
|
141
144
|
await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));
|
|
142
145
|
console.log(`\n📄 GitLab Code Quality report written to: ${outputFile}`);
|
|
143
146
|
console.log(`📊 ${result.totalFindings} issues exported`);
|
|
144
147
|
}
|
|
148
|
+
// src/check-categories.ts
|
|
149
|
+
/**
|
|
150
|
+
* Build a checkId -> check category name index from a parsed check variant
|
|
151
|
+
* form template document.
|
|
152
|
+
*
|
|
153
|
+
* The checkId key is the implementing class name; the value is the category
|
|
154
|
+
* object name (a stable key like 'CI_PERFORMANCE', not the display title).
|
|
155
|
+
*
|
|
156
|
+
* @param doc - Parsed form template document (nullable for convenience)
|
|
157
|
+
* @returns Map of checkId -> category name; empty when the document is
|
|
158
|
+
* missing or malformed
|
|
159
|
+
*/
|
|
160
|
+
function parseCheckCategoryIndex(doc) {
|
|
161
|
+
const index = /* @__PURE__ */ new Map();
|
|
162
|
+
const categories = doc?.formTemplate?.availableCheckCategories?.availableCheckCategory ?? [];
|
|
163
|
+
for (const category of categories) collectCategoryChecks(category, index);
|
|
164
|
+
return index;
|
|
165
|
+
}
|
|
166
|
+
/** Index every check of one category under the category's name. */
|
|
167
|
+
function collectCategoryChecks(category, index) {
|
|
168
|
+
const categoryName = category.checkCategoryObject?.name;
|
|
169
|
+
if (!categoryName) return;
|
|
170
|
+
const checkIds = (category.check ?? []).map((check) => check.checkObject?.content?.implementingClass?.name).filter((checkId) => !!checkId);
|
|
171
|
+
for (const checkId of checkIds) index.set(checkId, categoryName);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fetch the check variant form template and build the checkId -> category
|
|
175
|
+
* name index for a run.
|
|
176
|
+
*
|
|
177
|
+
* Best-effort: any error (network, 404, malformed document) is swallowed
|
|
178
|
+
* and reported through the optional logger so a failed enrichment never
|
|
179
|
+
* fails the ATC run itself.
|
|
180
|
+
*
|
|
181
|
+
* @param client - ADT client with the checkvariants.formtemplate surface
|
|
182
|
+
* @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')
|
|
183
|
+
* @param logger - Optional CLI logger for diagnostics
|
|
184
|
+
* @returns checkId -> category name index; empty when unavailable
|
|
185
|
+
*/
|
|
186
|
+
async function fetchCheckCategoryIndex(client, checkVariant, logger) {
|
|
187
|
+
try {
|
|
188
|
+
return parseCheckCategoryIndex(await client.adt.atc.checkvariants.formtemplate({ chkvName: checkVariant }));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger?.warn?.(`⚠️ Check category enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
+
return /* @__PURE__ */ new Map();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Enrich findings with their check category in place.
|
|
196
|
+
*
|
|
197
|
+
* Generic over any finding shape carrying a checkId (and an optional
|
|
198
|
+
* checkCategory field), so both the CLI plugin's AtcFinding and other
|
|
199
|
+
* canonical finding types can be enriched with the same index.
|
|
200
|
+
*
|
|
201
|
+
* @param findings - Findings to enrich (mutated in place)
|
|
202
|
+
* @param index - checkId -> category name index
|
|
203
|
+
* @returns Number of findings that received a category
|
|
204
|
+
*/
|
|
205
|
+
function enrichFindingsWithCategories(findings, index) {
|
|
206
|
+
let enriched = 0;
|
|
207
|
+
for (const finding of findings) {
|
|
208
|
+
const checkCategory = index.get(finding.checkId);
|
|
209
|
+
if (checkCategory) {
|
|
210
|
+
finding.checkCategory = checkCategory;
|
|
211
|
+
enriched++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return enriched;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Enrich an ATC result with check categories in place.
|
|
218
|
+
*
|
|
219
|
+
* @param result - The run result (findings mutated in place)
|
|
220
|
+
* @param index - checkId -> category name index
|
|
221
|
+
* @returns Number of findings that received a category
|
|
222
|
+
*/
|
|
223
|
+
function enrichResultWithCategories(result, index) {
|
|
224
|
+
return enrichFindingsWithCategories(result.findings ?? [], index);
|
|
225
|
+
}
|
|
226
|
+
// src/documentation-bridge.ts
|
|
227
|
+
/**
|
|
228
|
+
* Parse the implementing check class from an ATC finding documentation page.
|
|
229
|
+
*
|
|
230
|
+
* Live shape (2026-10-22): the page opens with
|
|
231
|
+
* "Appl. Comp. Check / Check Class / Message Code" followed by
|
|
232
|
+
* `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in
|
|
233
|
+
* that line is the implementing class the check variant form template lists
|
|
234
|
+
* — the bridge from the finding's GUID checkId to the check catalog.
|
|
235
|
+
*
|
|
236
|
+
* The match is bounded (no unbounded quantifiers on page input) and accepts
|
|
237
|
+
* an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).
|
|
238
|
+
*
|
|
239
|
+
* @param html - The documentation page HTML
|
|
240
|
+
* @returns The check class name, or undefined when the page does not carry it
|
|
241
|
+
*/
|
|
242
|
+
function parseCheckClassFromDocumentation(html) {
|
|
243
|
+
return html.match(/Check\s*Class[^<]{0,80}<\/h3>\s*<p>\s*[^/<]{0,120}\/\s*((?:\/[A-Za-z0-9_]{1,8}\/)?[A-Za-z][A-Za-z0-9_]*)\s*\//u)?.[1];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Read one documentation page and resolve its check class, with a shared
|
|
247
|
+
* per-URI cache so repeated findings never re-fetch the same page.
|
|
248
|
+
*/
|
|
249
|
+
async function resolveCheckClassForUri(documentationUri, context) {
|
|
250
|
+
if (context.classByDocumentationUri.has(documentationUri)) return;
|
|
251
|
+
try {
|
|
252
|
+
const html = await context.read(documentationUri, context.maxBytes, { headers: { Accept: "application/vnd.sap.adt.docu.v1+html" } });
|
|
253
|
+
context.classByDocumentationUri.set(documentationUri, parseCheckClassFromDocumentation(html));
|
|
254
|
+
} catch (error) {
|
|
255
|
+
context.logger?.warn?.(`⚠️ ATC documentation read failed for ${documentationUri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
256
|
+
context.classByDocumentationUri.set(documentationUri, void 0);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Bridge finding categories through the ATC documentation page.
|
|
261
|
+
*
|
|
262
|
+
* After the direct form-template index pass (which resolves findings whose
|
|
263
|
+
* checkId already matches a catalog implementing class), this resolves the
|
|
264
|
+
* GUID-checkId findings: each finding's documentation page names the check
|
|
265
|
+
* class, which resolves through the same index.
|
|
266
|
+
*
|
|
267
|
+
* Best-effort: per-documentation-URI fetch errors are reported through the
|
|
268
|
+
* optional logger and skipped; a finding keeps no category when its page
|
|
269
|
+
* cannot be fetched or parsed. Reads are deduplicated per URI.
|
|
270
|
+
*
|
|
271
|
+
* @param client - ADT client with an optional bounded text read
|
|
272
|
+
* @param findings - Findings to enrich in place
|
|
273
|
+
* @param classToCategory - checkId/class -> category name index (the
|
|
274
|
+
* form-template catalog: parseCheckCategoryIndex output)
|
|
275
|
+
* @param options - maxBytes (default 64 KiB) and an optional logger
|
|
276
|
+
* @returns Number of findings that received a category
|
|
277
|
+
*/
|
|
278
|
+
async function resolveCategoriesViaDocumentation(client, findings, classToCategory, options = {}) {
|
|
279
|
+
const read = client.readTextBounded;
|
|
280
|
+
if (!read || classToCategory.size === 0) return 0;
|
|
281
|
+
const pending = findings.filter((finding) => !finding.checkCategory && finding.documentationUri);
|
|
282
|
+
if (pending.length === 0) return 0;
|
|
283
|
+
const context = {
|
|
284
|
+
read,
|
|
285
|
+
maxBytes: options.maxBytes ?? 65536,
|
|
286
|
+
classByDocumentationUri: /* @__PURE__ */ new Map(),
|
|
287
|
+
logger: options.logger
|
|
288
|
+
};
|
|
289
|
+
let enriched = 0;
|
|
290
|
+
for (const finding of pending) {
|
|
291
|
+
await resolveCheckClassForUri(finding.documentationUri, context);
|
|
292
|
+
const checkClass = context.classByDocumentationUri.get(finding.documentationUri);
|
|
293
|
+
const checkCategory = checkClass ? classToCategory.get(checkClass) : void 0;
|
|
294
|
+
if (checkCategory) {
|
|
295
|
+
finding.checkCategory = checkCategory;
|
|
296
|
+
enriched++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return enriched;
|
|
300
|
+
}
|
|
145
301
|
// src/commands/atc.ts
|
|
146
302
|
/**
|
|
147
303
|
* Create OSC 8 hyperlink for terminal
|
|
@@ -161,6 +317,16 @@ function adtLink(name, uri, systemName) {
|
|
|
161
317
|
return hyperlink(chalk.cyan(name), url);
|
|
162
318
|
}
|
|
163
319
|
/**
|
|
320
|
+
* The trusted documentation link of one worklist finding.
|
|
321
|
+
*
|
|
322
|
+
* Live shape (2026-10-22): <atom:link rel="http://www.sap.com/adt/
|
|
323
|
+
* relations/documentation" href="/sap/bc/adt/documentation/atc/documents/
|
|
324
|
+
* itemid/{guid}/index/{n}"/> inside the atcfinding:finding element.
|
|
325
|
+
*/
|
|
326
|
+
function documentationUriFromFinding(finding) {
|
|
327
|
+
return finding.link?.find((link) => link.rel === "http://www.sap.com/adt/relations/documentation")?.href;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
164
330
|
* Convert worklist response to AtcResult
|
|
165
331
|
*/
|
|
166
332
|
function convertWorklistToResult(worklistResponse, checkVariant) {
|
|
@@ -179,7 +345,8 @@ function convertWorklistToResult(worklistResponse, checkVariant) {
|
|
|
179
345
|
objectType: obj.type || "",
|
|
180
346
|
objectName: obj.name || "",
|
|
181
347
|
location: finding.location,
|
|
182
|
-
findingUri: finding.uri
|
|
348
|
+
findingUri: finding.uri,
|
|
349
|
+
documentationUri: documentationUriFromFinding(finding)
|
|
183
350
|
});
|
|
184
351
|
}
|
|
185
352
|
}
|
|
@@ -353,6 +520,11 @@ const atcCommand = {
|
|
|
353
520
|
await client.adt.atc.runs.post({ worklistId }, runData);
|
|
354
521
|
ctx.logger.info("📊 Fetching results...");
|
|
355
522
|
const result = convertWorklistToResult(await client.adt.atc.worklists.get(worklistId, { includeExemptedFindings: "false" }), checkVariant);
|
|
523
|
+
const categoryIndex = await fetchCheckCategoryIndex(client, checkVariant, ctx.logger);
|
|
524
|
+
const enriched = enrichResultWithCategories(result, categoryIndex);
|
|
525
|
+
if (enriched > 0) ctx.logger.info(`🏷️ Enriched ${enriched}/${result.totalFindings} finding(s) with check categories`);
|
|
526
|
+
const bridged = await resolveCategoriesViaDocumentation(client, result.findings, categoryIndex, { logger: ctx.logger });
|
|
527
|
+
if (bridged > 0) ctx.logger.info(`🏷 Bridged ${bridged} finding(s) to check categories via ATC documentation`);
|
|
356
528
|
let resolver;
|
|
357
529
|
if (options.resolver) {
|
|
358
530
|
const name = options.resolver;
|
|
@@ -375,6 +547,6 @@ const atcCommand = {
|
|
|
375
547
|
if (result.errorCount > 0) process.exit(1);
|
|
376
548
|
}
|
|
377
549
|
};
|
|
378
|
-
export { outputGitLabCodeQuality as
|
|
550
|
+
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 };
|
|
379
551
|
|
|
380
|
-
//# sourceMappingURL=atc-
|
|
552
|
+
//# sourceMappingURL=atc-6cFtFkir.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atc-6cFtFkir.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\nexport async function outputSarifReport(\n result: AtcResult,\n outputFile: string,\n _targetName?: string,\n): Promise<void> {\n // Transform ATC findings to SARIF format\n const rules = Array.from(\n new Map(\n result.findings.map((f) => [\n f.checkId,\n {\n id: f.checkId,\n name: f.checkTitle || f.checkId,\n shortDescription: { text: f.checkTitle || f.checkId },\n fullDescription: { text: f.checkTitle || f.checkId },\n defaultConfiguration: {\n level:\n f.priority === 1\n ? 'error'\n : f.priority === 2\n ? 'warning'\n : 'note',\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n ...(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:\n finding.priority === 1\n ? 'error'\n : finding.priority === 2\n ? 'warning'\n : 'note',\n message: { text: finding.messageText },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: filePath },\n region: {\n startLine: line,\n startColumn: 1,\n },\n },\n },\n ],\n partialFingerprints: {\n primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}`,\n },\n };\n });\n\n const sarifReport = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'ABAP Test Cockpit',\n version: '1.0.0',\n informationUri:\n 'https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html',\n rules: Array.from(rules),\n },\n },\n results,\n },\n ],\n };\n\n await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));\n console.log(`\\n📄 SARIF report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalFindings} issues exported for GitHub Code Scanning`,\n );\n}\n","/**\n * GitLab Code Quality Formatter\n *\n * Outputs ATC findings in GitLab Code Quality format.\n *\n * This formatter is a pure output formatter — it maps ATC findings to the\n * GitLab Code Quality JSON structure. Path resolution and line number\n * conversion are delegated to an optional FindingResolver.\n *\n * Without a resolver, the formatter produces PREFIX-style paths and raw\n * ATC (method-relative) line numbers. With a resolver (e.g., from\n * @abapify/adt-plugin-abapgit), it produces correct git paths and\n * file-relative line numbers.\n *\n * @example\n * ```typescript\n * // Without resolver (standalone CLI usage)\n * await outputGitLabCodeQuality(result, 'report.json');\n *\n * // With resolver (inside an abapgit repo)\n * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';\n * const resolver = await createFindingResolver();\n * await outputGitLabCodeQuality(result, 'report.json', { resolver });\n * ```\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding, FindingResolver } from '../types';\n\n/**\n * Options for the GitLab Code Quality formatter\n */\nexport interface GitLabFormatterOptions {\n /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */\n resolver?: FindingResolver;\n}\n\n// ── Extract method name from ATC location URI ───────────────────────────\n// Format 1: /sap/bc/adt/oo/classes/zcl_foo/methods/my_method#start=21,0\n// Format 2: ...#type=CLAS%2FOM;name=MY_METHOD;start=21\n\nfunction extractMethodName(location?: string): string | undefined {\n if (!location) return undefined;\n const match =\n location.match(/\\/methods\\/(\\w+)/i) || location.match(/[;?&]name=(\\w+)/i);\n return match ? match[1].toLowerCase() : undefined;\n}\n\n// ── Construct PREFIX-style fallback path ─────────────────────────────────\n\nfunction prefixPath(finding: AtcFinding): string {\n return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;\n}\n\n// ── Main formatter ──────────────────────────────────────────────────────\n\nexport async function outputGitLabCodeQuality(\n result: AtcResult,\n outputFile: string,\n options?: GitLabFormatterOptions,\n): Promise<void> {\n const resolver = options?.resolver;\n\n // Transform ATC findings to GitLab Code Quality format\n const gitlabReport = await Promise.all(\n result.findings.map(async (finding: AtcFinding) => {\n // Map ATC priority to GitLab severity\n let severity: string;\n switch (finding.priority) {\n case 1:\n severity = 'blocker';\n break;\n case 2:\n severity = 'major';\n break;\n case 3:\n severity = 'minor';\n break;\n default:\n severity = 'info';\n break;\n }\n\n // Parse ATC line number from location\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const atcLine = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n // Extract method name from ATC location URI\n const methodName = extractMethodName(finding.location);\n\n // Resolve path and line via plugin, or fall back to PREFIX-style\n let filePath: string;\n let fileLine: number;\n\n if (resolver) {\n const resolved = await resolver.resolve(\n finding.objectType,\n finding.objectName,\n atcLine,\n methodName,\n finding.location,\n );\n if (resolved) {\n filePath = resolved.path;\n fileLine = resolved.line;\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n\n // Create unique fingerprint\n const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;\n\n return {\n description: finding.messageText,\n check_name: finding.checkTitle || finding.checkId,\n // 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 = html.match(\n /Check\\s*Class[^<]{0,80}<\\/h3>\\s*<p>\\s*[^/<]{0,120}\\/\\s*((?:\\/[A-Za-z0-9_]{1,8}\\/)?[A-Za-z][A-Za-z0-9_]*)\\s*\\//u,\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/** Shared state for the per-URI documentation fetch. */\ninterface DocumentationFetchContext {\n read: NonNullable<DocumentationReadingClient['readTextBounded']>;\n maxBytes: 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 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 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 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 const priorityIcon =\n finding.priority === 1 ? '❌' : finding.priority === 2 ? '⚠️' : 'ℹ️';\n const objectLink = finding.location\n ? adtLink(finding.objectName, finding.location, systemName)\n : finding.objectName;\n console.log(\n ` ${priorityIcon} ${finding.checkTitle || finding.checkId}`,\n );\n console.log(` ${finding.messageText}`);\n console.log(` Object: ${objectLink} (${finding.objectType})`);\n });\n\n if (result.findings.length > 5) {\n console.log(` ... (${result.findings.length - 5} more findings)`);\n }\n }\n\n console.log(`\\n💡 Use --debug for detailed results`);\n}\n\n/**\n * ATC Command Plugin\n */\nexport const atcCommand: CliCommandPlugin = {\n name: 'atc',\n description: 'Run ABAP Test Cockpit (ATC) checks',\n\n options: [\n { flags: '-p, --package <package>', description: 'Run ATC on package' },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run ATC on transport request (e.g., TRLK942970)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run ATC on objects listed in file (one URI per line)',\n },\n {\n flags: '--variant <variant>',\n description: 'ATC check variant (default: from system customizing)',\n },\n {\n flags: '--max-results <number>',\n description: 'Maximum number of results',\n default: '100',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, gitlab, sarif',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description: 'Output file (required for gitlab/sarif format)',\n },\n {\n flags: '--resolver <name>',\n description:\n 'Finding resolver plugin for path/line resolution (e.g., abapgit)',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n transport?: string;\n fromFile?: string;\n variant?: string;\n maxResults?: string;\n format?: OutputFormat;\n output?: string;\n resolver?: string;\n };\n\n // Validate mutually exclusive options\n const targetCount = [\n options.package,\n options.object,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for gitlab/sarif formats\n if (\n (options.format === 'gitlab' || options.format === 'sarif') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client from context\n if (!ctx.getAdtClient) {\n ctx.logger.error(\n '❌ ADT client not available. This command requires authentication.',\n );\n ctx.logger.error(' Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n ctx.logger.info('🔍 Running ABAP Test Cockpit checks...');\n\n // Determine target(s)\n let targetUris: string[];\n let targetName: string;\n\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n targetUris = fileContent\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#')); // Skip empty lines and comments\n\n if (targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n } else if (options.transport) {\n targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];\n targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n } else if (options.package) {\n targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];\n targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n } else {\n targetUris = [options.object!];\n targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n }\n\n // Step 1: Get ATC customizing to find default check variant\n ctx.logger.info('📋 Reading ATC customizing...');\n const customizing = await client.adt.atc.customizing.get();\n\n // Find systemCheckVariant in properties\n let checkVariant = options.variant;\n if (!checkVariant) {\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n if (variantProp?.value) {\n checkVariant = variantProp.value;\n } else {\n checkVariant = 'DEFAULT';\n }\n }\n ctx.logger.info(`🎯 Check variant: ${checkVariant}`);\n\n // Step 2: Create worklist (just with checkVariant, no objects)\n ctx.logger.info('📋 Creating ATC worklist...');\n const worklistCreateResponse = await client.adt.atc.worklists.create({\n checkVariant,\n });\n\n // Extract worklist ID - SAP can return plain string or object\n let worklistId: string;\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = worklistCreateResponse.match(/id=\"([^\"]+)\"/);\n worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();\n } else if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n worklistId = worklistCreateResponse.worklist.id;\n } else if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n worklistId = worklistCreateResponse.worklistRun.worklistId;\n } else {\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n }\n ctx.logger.info(`📋 Worklist created: ${worklistId}`);\n\n // Step 3: Run ATC checks with objects\n ctx.logger.info('⏳ Running ATC analysis...');\n const maxResults = Number.parseInt(options.maxResults || '100', 10);\n const runData = {\n run: {\n maximumVerdicts: maxResults,\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n await client.adt.atc.runs.post({ worklistId }, runData);\n\n // Step 4: Get results\n ctx.logger.info('📊 Fetching results...');\n const worklistResult = await client.adt.atc.worklists.get(worklistId, {\n includeExemptedFindings: 'false',\n });\n\n // Convert to AtcResult\n const result = convertWorklistToResult(\n worklistResult as WorklistResponse,\n checkVariant,\n );\n\n // Enrich findings with check categories from the run's check variant\n // form template (best-effort: never fails the run itself)\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 let resolver: import('../types').FindingResolver | undefined;\n if (options.resolver) {\n const name = options.resolver;\n if (name === 'abapgit') {\n // Built-in abapgit resolver — no external dependencies\n const { createAbapGitResolver } = await import('../resolvers/abapgit');\n resolver = createAbapGitResolver();\n } else {\n // External resolver — try dynamic import\n try {\n ctx.logger.info(`📂 Loading external resolver: ${name}`);\n const mod = await import(name);\n if (typeof mod.createFindingResolver === 'function') {\n resolver = mod.createFindingResolver();\n } else {\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n }\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n }\n }\n\n // Output based on format\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'gitlab' && options.output) {\n await outputGitLabCodeQuality(result, options.output, { resolver });\n } else if (options.format === 'sarif' && options.output) {\n await outputSarifReport(result, options.output, targetName);\n } else {\n displayAtcResults(result, ctx.adtSystemName);\n }\n\n // Exit with error code if there are errors\n if (result.errorCount > 0) {\n process.exit(1);\n }\n },\n};\n\nexport default atcCommand;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;cA6Oa,YAAY"}
|
package/dist/commands/atc.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as atcCommand, t as default } from "../atc-
|
|
1
|
+
export { t as atcCommand, t as default } from "../atc-6cFtFkir.mjs";
|
package/dist/index.d.mts
CHANGED
|
@@ -20,6 +20,20 @@ interface AtcFinding {
|
|
|
20
20
|
objectName: string;
|
|
21
21
|
location?: string;
|
|
22
22
|
findingUri?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Trusted ATC documentation link for this occurrence (the worklist
|
|
25
|
+
* atom:link). The documentation page names the check class
|
|
26
|
+
* ("Appl. Comp. Check / Check Class / Message Code" header line), which
|
|
27
|
+
* is the bridge from the finding's GUID checkId to the check catalog.
|
|
28
|
+
*/
|
|
29
|
+
documentationUri?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Check category name (e.g., 'CI_PERFORMANCE') the finding's check is
|
|
32
|
+
* assigned to, resolved from the run's check variant form template.
|
|
33
|
+
* Optional: enrichment is best-effort and absent when the form template
|
|
34
|
+
* is unavailable or the checkId is not in its catalog.
|
|
35
|
+
*/
|
|
36
|
+
checkCategory?: string;
|
|
23
37
|
}
|
|
24
38
|
/**
|
|
25
39
|
* ATC Result summary
|
|
@@ -86,6 +100,206 @@ interface GitLabFormatterOptions {
|
|
|
86
100
|
resolver?: FindingResolver;
|
|
87
101
|
}
|
|
88
102
|
declare function outputGitLabCodeQuality(result: AtcResult, outputFile: string, options?: GitLabFormatterOptions): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Check Category Enrichment
|
|
105
|
+
*
|
|
106
|
+
* Resolves ATC findings to their check category (e.g., 'CI_PERFORMANCE',
|
|
107
|
+
* 'CI_SECURITY') through the check variant form template
|
|
108
|
+
* (GET /sap/bc/adt/atc/checkvariants/formtemplate?chkvName={name}).
|
|
109
|
+
*
|
|
110
|
+
* The form template is the check catalog of the run's check variant: every
|
|
111
|
+
* available check category lists its checks, and each check carries its
|
|
112
|
+
* implementing class. When a finding's checkId matches a catalog
|
|
113
|
+
* implementing class, the index resolves it directly. Live worklist
|
|
114
|
+
* checkIds are check-instance GUIDs (observed on a live S/4 system,
|
|
115
|
+
* 2026-10-22); those resolve through the documentation bridge in
|
|
116
|
+
* ./documentation-bridge.ts.
|
|
117
|
+
*
|
|
118
|
+
* Enrichment is best-effort by design: without the form template, or for a
|
|
119
|
+
* checkId outside the catalog, findings keep no category and the run still
|
|
120
|
+
* succeeds.
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* Structural shape of the parsed check variant form template document
|
|
124
|
+
* (GET /sap/bc/adt/atc/checkvariants/formtemplate).
|
|
125
|
+
*
|
|
126
|
+
* Only the fields needed for the checkId -> category index are declared.
|
|
127
|
+
*/
|
|
128
|
+
interface FormTemplateDocument {
|
|
129
|
+
formTemplate?: {
|
|
130
|
+
availableCheckCategories?: {
|
|
131
|
+
availableCheckCategory?: FormTemplateCategoryEntry[];
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** One available check category with the checks it contains. */
|
|
136
|
+
interface FormTemplateCategoryEntry {
|
|
137
|
+
checkCategoryObject?: {
|
|
138
|
+
/** Category name (the enrichment key, e.g., 'CI_PERFORMANCE') */
|
|
139
|
+
name?: string;
|
|
140
|
+
/** Category display title (e.g., 'Performance') */
|
|
141
|
+
description?: string;
|
|
142
|
+
};
|
|
143
|
+
check?: FormTemplateCheckEntry[];
|
|
144
|
+
}
|
|
145
|
+
/** One check entry of a category. */
|
|
146
|
+
interface FormTemplateCheckEntry {
|
|
147
|
+
checkObject?: {
|
|
148
|
+
/** Check name (e.g., 'CI_ANALYZE_WHERE') */
|
|
149
|
+
name?: string;
|
|
150
|
+
content?: {
|
|
151
|
+
/** The check's category assignment (the category name) */
|
|
152
|
+
category?: {
|
|
153
|
+
name?: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Implementing class of the check (e.g., 'CL_CI_TEST_SELECT_TAW_A').
|
|
157
|
+
* Worklist findings carry a check-instance GUID instead; the
|
|
158
|
+
* documentation bridge resolves that GUID to this class name.
|
|
159
|
+
*/
|
|
160
|
+
implementingClass?: {
|
|
161
|
+
name?: string;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build a checkId -> check category name index from a parsed check variant
|
|
168
|
+
* form template document.
|
|
169
|
+
*
|
|
170
|
+
* The checkId key is the implementing class name; the value is the category
|
|
171
|
+
* object name (a stable key like 'CI_PERFORMANCE', not the display title).
|
|
172
|
+
*
|
|
173
|
+
* @param doc - Parsed form template document (nullable for convenience)
|
|
174
|
+
* @returns Map of checkId -> category name; empty when the document is
|
|
175
|
+
* missing or malformed
|
|
176
|
+
*/
|
|
177
|
+
declare function parseCheckCategoryIndex(doc: FormTemplateDocument | null | undefined): Map<string, string>;
|
|
178
|
+
/** Structural client surface needed to fetch a form template. */
|
|
179
|
+
interface FormTemplateClient {
|
|
180
|
+
adt: {
|
|
181
|
+
atc: {
|
|
182
|
+
checkvariants: {
|
|
183
|
+
/** GET /sap/bc/adt/atc/checkvariants/formtemplate{?chkvName} */
|
|
184
|
+
formtemplate: (params?: {
|
|
185
|
+
chkvName?: string;
|
|
186
|
+
}) => Promise<FormTemplateDocument>;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Fetch the check variant form template and build the checkId -> category
|
|
193
|
+
* name index for a run.
|
|
194
|
+
*
|
|
195
|
+
* Best-effort: any error (network, 404, malformed document) is swallowed
|
|
196
|
+
* and reported through the optional logger so a failed enrichment never
|
|
197
|
+
* fails the ATC run itself.
|
|
198
|
+
*
|
|
199
|
+
* @param client - ADT client with the checkvariants.formtemplate surface
|
|
200
|
+
* @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')
|
|
201
|
+
* @param logger - Optional CLI logger for diagnostics
|
|
202
|
+
* @returns checkId -> category name index; empty when unavailable
|
|
203
|
+
*/
|
|
204
|
+
declare function fetchCheckCategoryIndex(client: FormTemplateClient, checkVariant: string, logger?: {
|
|
205
|
+
warn: (message: string) => void;
|
|
206
|
+
info?: (message: string) => void;
|
|
207
|
+
}): Promise<Map<string, string>>;
|
|
208
|
+
/**
|
|
209
|
+
* Enrich findings with their check category in place.
|
|
210
|
+
*
|
|
211
|
+
* Generic over any finding shape carrying a checkId (and an optional
|
|
212
|
+
* checkCategory field), so both the CLI plugin's AtcFinding and other
|
|
213
|
+
* canonical finding types can be enriched with the same index.
|
|
214
|
+
*
|
|
215
|
+
* @param findings - Findings to enrich (mutated in place)
|
|
216
|
+
* @param index - checkId -> category name index
|
|
217
|
+
* @returns Number of findings that received a category
|
|
218
|
+
*/
|
|
219
|
+
declare function enrichFindingsWithCategories<T extends {
|
|
220
|
+
checkId: string;
|
|
221
|
+
checkCategory?: string;
|
|
222
|
+
}>(findings: T[], index: Map<string, string>): number;
|
|
223
|
+
/**
|
|
224
|
+
* Enrich an ATC result with check categories in place.
|
|
225
|
+
*
|
|
226
|
+
* @param result - The run result (findings mutated in place)
|
|
227
|
+
* @param index - checkId -> category name index
|
|
228
|
+
* @returns Number of findings that received a category
|
|
229
|
+
*/
|
|
230
|
+
declare function enrichResultWithCategories<T extends {
|
|
231
|
+
checkId: string;
|
|
232
|
+
checkCategory?: string;
|
|
233
|
+
}>(result: {
|
|
234
|
+
findings?: T[];
|
|
235
|
+
}, index: Map<string, string>): number;
|
|
236
|
+
/**
|
|
237
|
+
* Documentation Bridge for Check Category Enrichment
|
|
238
|
+
*
|
|
239
|
+
* ATC worklist findings carry a check-instance GUID as their checkId — not
|
|
240
|
+
* the implementing class name the check variant form template catalog lists
|
|
241
|
+
* (observed on a live S/4 system, 2026-10-22). The finding's documentation
|
|
242
|
+
* page (the worklist atom:link) names the check class in its header line,
|
|
243
|
+
* which resolves through the form-template category index.
|
|
244
|
+
*
|
|
245
|
+
* Everything here is best-effort: fetch errors are reported through the
|
|
246
|
+
* optional logger and skipped; findings keep no category when their page
|
|
247
|
+
* cannot be fetched or parsed.
|
|
248
|
+
*/
|
|
249
|
+
/** Structural surface for a bounded documentation read. The real ADT client
|
|
250
|
+
* provides readTextBounded; mocks without it simply skip the bridge. */
|
|
251
|
+
interface DocumentationReadingClient {
|
|
252
|
+
readTextBounded?: (uri: string, maxBytes: number, options?: {
|
|
253
|
+
headers?: Record<string, string>;
|
|
254
|
+
}) => Promise<string>;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Parse the implementing check class from an ATC finding documentation page.
|
|
258
|
+
*
|
|
259
|
+
* Live shape (2026-10-22): the page opens with
|
|
260
|
+
* "Appl. Comp. Check / Check Class / Message Code" followed by
|
|
261
|
+
* `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in
|
|
262
|
+
* that line is the implementing class the check variant form template lists
|
|
263
|
+
* — the bridge from the finding's GUID checkId to the check catalog.
|
|
264
|
+
*
|
|
265
|
+
* The match is bounded (no unbounded quantifiers on page input) and accepts
|
|
266
|
+
* an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).
|
|
267
|
+
*
|
|
268
|
+
* @param html - The documentation page HTML
|
|
269
|
+
* @returns The check class name, or undefined when the page does not carry it
|
|
270
|
+
*/
|
|
271
|
+
declare function parseCheckClassFromDocumentation(html: string): string | undefined;
|
|
272
|
+
/** Finding shape the bridge enriches in place. */
|
|
273
|
+
interface DocumentationBridgeFinding {
|
|
274
|
+
checkId: string;
|
|
275
|
+
checkCategory?: string;
|
|
276
|
+
documentationUri?: string;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Bridge finding categories through the ATC documentation page.
|
|
280
|
+
*
|
|
281
|
+
* After the direct form-template index pass (which resolves findings whose
|
|
282
|
+
* checkId already matches a catalog implementing class), this resolves the
|
|
283
|
+
* GUID-checkId findings: each finding's documentation page names the check
|
|
284
|
+
* class, which resolves through the same index.
|
|
285
|
+
*
|
|
286
|
+
* Best-effort: per-documentation-URI fetch errors are reported through the
|
|
287
|
+
* optional logger and skipped; a finding keeps no category when its page
|
|
288
|
+
* cannot be fetched or parsed. Reads are deduplicated per URI.
|
|
289
|
+
*
|
|
290
|
+
* @param client - ADT client with an optional bounded text read
|
|
291
|
+
* @param findings - Findings to enrich in place
|
|
292
|
+
* @param classToCategory - checkId/class -> category name index (the
|
|
293
|
+
* form-template catalog: parseCheckCategoryIndex output)
|
|
294
|
+
* @param options - maxBytes (default 64 KiB) and an optional logger
|
|
295
|
+
* @returns Number of findings that received a category
|
|
296
|
+
*/
|
|
297
|
+
declare function resolveCategoriesViaDocumentation(client: DocumentationReadingClient, findings: DocumentationBridgeFinding[], classToCategory: ReadonlyMap<string, string>, options?: {
|
|
298
|
+
maxBytes?: number;
|
|
299
|
+
logger?: {
|
|
300
|
+
warn: (message: string) => void;
|
|
301
|
+
};
|
|
302
|
+
}): Promise<number>;
|
|
89
303
|
/**
|
|
90
304
|
* Create a built-in abapGit finding resolver.
|
|
91
305
|
*
|
|
@@ -109,5 +323,5 @@ declare function createAbapGitResolver(srcRoot?: string): FindingResolver;
|
|
|
109
323
|
* callers resolve that prefix against the repository's actual source tree.
|
|
110
324
|
*/
|
|
111
325
|
declare function adtUriToAbapGitPath(uri: string): string | null;
|
|
112
|
-
export { type AtcFinding, type AtcResult, type FindingResolver, type OutputFormat, type ResolvedLocation, adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
|
|
326
|
+
export { type AtcFinding, type AtcResult, type DocumentationBridgeFinding, type DocumentationReadingClient, type FindingResolver, type FormTemplateCategoryEntry, type FormTemplateCheckEntry, type FormTemplateClient, type FormTemplateDocument, type OutputFormat, type ResolvedLocation, adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, enrichFindingsWithCategories, enrichResultWithCategories, fetchCheckCategoryIndex, outputGitLabCodeQuality, outputSarifReport, parseCheckCategoryIndex, parseCheckClassFromDocumentation, resolveCategoriesViaDocumentation };
|
|
113
327
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/commands/atc-customizing.ts","../src/types.ts","../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/resolvers/abapgit.ts","../src/resolvers/adt-uri-to-abapgit-path.ts"],"mappings":";;cAiCa,uBAAuB;;;;;;;;;UCxBnB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;UAMe;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;;;;;KAMA;;;;UAKK;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;UAsBe;;;;;;;;;;;EAWf,QACE,oBACA,oBACA,iBACA,qBACA,uBACC,QAAQ;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/commands/atc-customizing.ts","../src/types.ts","../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/check-categories.ts","../src/documentation-bridge.ts","../src/resolvers/abapgit.ts","../src/resolvers/adt-uri-to-abapgit-path.ts"],"mappings":";;cAiCa,uBAAuB;;;;;;;;;UCxBnB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;EAOA;;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;;;;;KAMA;;;;UAKK;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;UAsBe;;;;;;;;;;;EAWf,QACE,oBACA,oBACA,iBACA,qBACA,uBACC,QAAQ;;iBC1FS,kBACpB,QAAQ,WACR,oBACA,uBACC;;;;UCmBc;;EAEf,WAAW;;iBAsBS,wBACpB,QAAQ,WACR,oBACA,UAAU,yBACT;;;;;;;;;;;;;;;;;;;;;;;;;;UClCc;EACf;IACE;MACE,yBAAyB;;;;;UAMd;EACf;;IAEE;;IAEA;;EAEF,QAAQ;;;UAIO;EACf;;IAEE;IACA;;MAEE;QAAa;;;;;;;MAMb;QAAsB;;;;;;;;;;;;;;;;iBAgBZ,wBACd,KAAK,0CACJ;;UA0Bc;EACf;IACE;MACE;;QAEE,eAAe;UACb;cACI,QAAQ;;;;;;;;;;;;;;;;;;iBAmBA,wBACpB,QAAQ,oBACR,sBACA;EACE,OAAO;EACP,QAAQ;IAET,QAAQ;;;;;;;;;;;;iBA2BK,6BACd;EAAY;EAAiB;GAC7B,UAAU,KAAK,OAAO;;;;;;;;iBAmBR,2BACd;EAAY;EAAiB;GAC7B;EAAU,WAAW;GAAO,OAAO;;;;;;;;;;;;;;;;UCzKpB;EACf,mBACE,aACA,kBACA;IAAY,UAAU;QACnB;;;;;;;;;;;;;;;;;iBAkBS,iCACd;;UASe;EACf;EACA;EACA;;;;;;;;;;;;;;;;;;;;;iBAyDoB,kCACpB,QAAQ,4BACR,UAAU,8BACV,iBAAiB,6BACjB;EACE;EACA;IAAW,OAAO;;IAEnB;;;;;;;;;iBC0Ka,sBAAsB,mBAAmB;;;;;;;;;;;;;;iBCzHzC,oBAAoB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as createAbapGitResolver, r as adtUriToAbapGitPath } from "./abapgit-B2E5I-u0.mjs";
|
|
2
|
-
import {
|
|
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-6cFtFkir.mjs";
|
|
3
3
|
// src/commands/atc-customizing.ts
|
|
4
4
|
const atcCustomizingCommand = {
|
|
5
5
|
name: "atc-customizing",
|
|
@@ -41,6 +41,6 @@ const atcCustomizingCommand = {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
-
export { adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
|
|
44
|
+
export { adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, enrichFindingsWithCategories, enrichResultWithCategories, fetchCheckCategoryIndex, outputGitLabCodeQuality, outputSarifReport, parseCheckCategoryIndex, parseCheckClassFromDocumentation, resolveCategoriesViaDocumentation };
|
|
45
45
|
|
|
46
46
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/adt-atc",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
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.
|
|
31
|
+
"@abapify/adt-plugin": "0.4.21",
|
|
32
32
|
"chalk": "^5.3.0"
|
|
33
33
|
},
|
|
34
34
|
"module": "./dist/index.mjs",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"atc-BEoE4xIY.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/commands/atc.ts"],"sourcesContent":["/**\n * SARIF Formatter\n *\n * Outputs ATC findings in SARIF format for GitHub Code Scanning.\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding } from '../types';\n\nexport async function outputSarifReport(\n result: AtcResult,\n outputFile: string,\n _targetName?: string,\n): Promise<void> {\n // Transform ATC findings to SARIF format\n const rules = Array.from(\n new Map(\n result.findings.map((f) => [\n f.checkId,\n {\n id: f.checkId,\n name: f.checkTitle || f.checkId,\n shortDescription: { text: f.checkTitle || f.checkId },\n fullDescription: { text: f.checkTitle || f.checkId },\n defaultConfiguration: {\n level:\n f.priority === 1\n ? 'error'\n : f.priority === 2\n ? 'warning'\n : 'note',\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n },\n },\n ]),\n ).values(),\n );\n\n const results = result.findings.map((finding: AtcFinding) => {\n const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;\n\n // Parse line number from location if available\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const line = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n return {\n ruleId: finding.checkId,\n level:\n finding.priority === 1\n ? 'error'\n : finding.priority === 2\n ? 'warning'\n : 'note',\n message: { text: finding.messageText },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: filePath },\n region: {\n startLine: line,\n startColumn: 1,\n },\n },\n },\n ],\n partialFingerprints: {\n primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}`,\n },\n };\n });\n\n const sarifReport = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'ABAP Test Cockpit',\n version: '1.0.0',\n informationUri:\n 'https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html',\n rules: Array.from(rules),\n },\n },\n results,\n },\n ],\n };\n\n await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));\n console.log(`\\n📄 SARIF report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalFindings} issues exported for GitHub Code Scanning`,\n );\n}\n","/**\n * GitLab Code Quality Formatter\n *\n * Outputs ATC findings in GitLab Code Quality format.\n *\n * This formatter is a pure output formatter — it maps ATC findings to the\n * GitLab Code Quality JSON structure. Path resolution and line number\n * conversion are delegated to an optional FindingResolver.\n *\n * Without a resolver, the formatter produces PREFIX-style paths and raw\n * ATC (method-relative) line numbers. With a resolver (e.g., from\n * @abapify/adt-plugin-abapgit), it produces correct git paths and\n * file-relative line numbers.\n *\n * @example\n * ```typescript\n * // Without resolver (standalone CLI usage)\n * await outputGitLabCodeQuality(result, 'report.json');\n *\n * // With resolver (inside an abapgit repo)\n * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';\n * const resolver = await createFindingResolver();\n * await outputGitLabCodeQuality(result, 'report.json', { resolver });\n * ```\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding, FindingResolver } from '../types';\n\n/**\n * Options for the GitLab Code Quality formatter\n */\nexport interface GitLabFormatterOptions {\n /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */\n resolver?: FindingResolver;\n}\n\n// ── Extract method name from ATC location URI ───────────────────────────\n// Format 1: /sap/bc/adt/oo/classes/zcl_foo/methods/my_method#start=21,0\n// Format 2: ...#type=CLAS%2FOM;name=MY_METHOD;start=21\n\nfunction extractMethodName(location?: string): string | undefined {\n if (!location) return undefined;\n const match =\n location.match(/\\/methods\\/(\\w+)/i) || location.match(/[;?&]name=(\\w+)/i);\n return match ? match[1].toLowerCase() : undefined;\n}\n\n// ── Construct PREFIX-style fallback path ─────────────────────────────────\n\nfunction prefixPath(finding: AtcFinding): string {\n return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;\n}\n\n// ── Main formatter ──────────────────────────────────────────────────────\n\nexport async function outputGitLabCodeQuality(\n result: AtcResult,\n outputFile: string,\n options?: GitLabFormatterOptions,\n): Promise<void> {\n const resolver = options?.resolver;\n\n // Transform ATC findings to GitLab Code Quality format\n const gitlabReport = await Promise.all(\n result.findings.map(async (finding: AtcFinding) => {\n // Map ATC priority to GitLab severity\n let severity: string;\n switch (finding.priority) {\n case 1:\n severity = 'blocker';\n break;\n case 2:\n severity = 'major';\n break;\n case 3:\n severity = 'minor';\n break;\n default:\n severity = 'info';\n break;\n }\n\n // Parse ATC line number from location\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const atcLine = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n // Extract method name from ATC location URI\n const methodName = extractMethodName(finding.location);\n\n // Resolve path and line via plugin, or fall back to PREFIX-style\n let filePath: string;\n let fileLine: number;\n\n if (resolver) {\n const resolved = await resolver.resolve(\n finding.objectType,\n finding.objectName,\n atcLine,\n methodName,\n finding.location,\n );\n if (resolved) {\n filePath = resolved.path;\n fileLine = resolved.line;\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n\n // Create unique fingerprint\n const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;\n\n return {\n description: finding.messageText,\n check_name: finding.checkTitle || finding.checkId,\n fingerprint,\n severity,\n location: {\n path: filePath,\n lines: {\n begin: fileLine,\n end: fileLine,\n },\n },\n // Extra fields for downstream processing\n ...(methodName && { method: methodName }),\n ...(finding.location && { atc_location: finding.location }),\n };\n }),\n );\n\n await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));\n console.log(`\\n📄 GitLab Code Quality report written to: ${outputFile}`);\n console.log(`📊 ${result.totalFindings} issues exported`);\n}\n","/**\n * ATC Command Plugin\n *\n * CLI-agnostic command for running ABAP Test Cockpit checks.\n * Uses the CliContext.getAdtClient() factory for API access.\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport chalk from 'chalk';\nimport { outputSarifReport, outputGitLabCodeQuality } from '../formatters';\nimport type { AtcResult, AtcFinding, OutputFormat } from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.atc namespace provides all ATC operations\ninterface AdtClient {\n adt: {\n atc: {\n customizing: {\n get: () => Promise<{\n customizing: {\n properties: { property?: Array<{ name: string; value?: string }> };\n };\n }>;\n };\n worklists: {\n create: (params: {\n checkVariant: string;\n }) => Promise<\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } }\n >;\n get: (\n id: string,\n params: { includeExemptedFindings: string },\n ) => Promise<WorklistResponse>;\n };\n runs: {\n post: (\n params: { worklistId: string },\n body: {\n run: {\n maximumVerdicts: number;\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n },\n ) => Promise<void>;\n };\n };\n };\n}\n\n// Worklist response type\ninterface WorklistResponse {\n worklist?: {\n id: string;\n timestamp: string;\n objects: {\n object?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n findings: {\n finding?: Array<{\n uri?: string;\n location?: string;\n priority?: string;\n checkId?: string;\n checkTitle?: string;\n messageId?: string;\n messageTitle?: string;\n }>;\n };\n }>;\n };\n };\n worklistRun?: { worklistId: string };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? chalk.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(chalk.cyan(name), url);\n}\n\n/**\n * Convert worklist response to AtcResult\n */\nfunction convertWorklistToResult(\n worklistResponse: WorklistResponse,\n checkVariant: string,\n): AtcResult {\n const findings: AtcFinding[] = [];\n\n if (worklistResponse.worklist) {\n const objects = worklistResponse.worklist.objects.object || [];\n\n for (const obj of objects) {\n const objFindings = obj.findings.finding || [];\n for (const finding of objFindings) {\n findings.push({\n checkId: finding.checkId || '',\n checkTitle: finding.checkTitle || '',\n messageId: finding.messageId || '',\n priority: Number.parseInt(finding.priority || '3', 10),\n messageText: finding.messageTitle || '',\n objectUri: obj.uri || '',\n objectType: obj.type || '',\n objectName: obj.name || '',\n location: finding.location,\n findingUri: finding.uri,\n });\n }\n }\n }\n\n const errorCount = findings.filter((f) => f.priority === 1).length;\n const warningCount = findings.filter((f) => f.priority === 2).length;\n const infoCount = findings.filter((f) => f.priority >= 3).length;\n\n return {\n checkVariant,\n totalFindings: findings.length,\n errorCount,\n warningCount,\n infoCount,\n findings,\n };\n}\n\n/**\n * Display ATC results in console\n */\nfunction displayAtcResults(result: AtcResult, systemName?: string): void {\n if (result.totalFindings === 0) {\n console.log(`\\n✅ ATC check passed - No issues found!`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n return;\n }\n\n console.log(`\\n📊 ATC Results Summary:`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n if (result.errorCount > 0) console.log(` ❌ Errors: ${result.errorCount}`);\n if (result.warningCount > 0)\n console.log(` ⚠️ Warnings: ${result.warningCount}`);\n if (result.infoCount > 0) console.log(` ℹ️ Info: ${result.infoCount}`);\n console.log(` 📋 Total Issues: ${result.totalFindings}`);\n\n if (result.findings && result.findings.length > 0) {\n console.log(`\\n📄 Findings:`);\n result.findings.slice(0, 5).forEach((finding) => {\n const priorityIcon =\n finding.priority === 1 ? '❌' : finding.priority === 2 ? '⚠️' : 'ℹ️';\n const objectLink = finding.location\n ? adtLink(finding.objectName, finding.location, systemName)\n : finding.objectName;\n console.log(\n ` ${priorityIcon} ${finding.checkTitle || finding.checkId}`,\n );\n console.log(` ${finding.messageText}`);\n console.log(` Object: ${objectLink} (${finding.objectType})`);\n });\n\n if (result.findings.length > 5) {\n console.log(` ... (${result.findings.length - 5} more findings)`);\n }\n }\n\n console.log(`\\n💡 Use --debug for detailed results`);\n}\n\n/**\n * ATC Command Plugin\n */\nexport const atcCommand: CliCommandPlugin = {\n name: 'atc',\n description: 'Run ABAP Test Cockpit (ATC) checks',\n\n options: [\n { flags: '-p, --package <package>', description: 'Run ATC on package' },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run ATC on transport request (e.g., TRLK942970)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run ATC on objects listed in file (one URI per line)',\n },\n {\n flags: '--variant <variant>',\n description: 'ATC check variant (default: from system customizing)',\n },\n {\n flags: '--max-results <number>',\n description: 'Maximum number of results',\n default: '100',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, gitlab, sarif',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description: 'Output file (required for gitlab/sarif format)',\n },\n {\n flags: '--resolver <name>',\n description:\n 'Finding resolver plugin for path/line resolution (e.g., abapgit)',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n transport?: string;\n fromFile?: string;\n variant?: string;\n maxResults?: string;\n format?: OutputFormat;\n output?: string;\n resolver?: string;\n };\n\n // Validate mutually exclusive options\n const targetCount = [\n options.package,\n options.object,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for gitlab/sarif formats\n if (\n (options.format === 'gitlab' || options.format === 'sarif') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client from context\n if (!ctx.getAdtClient) {\n ctx.logger.error(\n '❌ ADT client not available. This command requires authentication.',\n );\n ctx.logger.error(' Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n ctx.logger.info('🔍 Running ABAP Test Cockpit checks...');\n\n // Determine target(s)\n let targetUris: string[];\n let targetName: string;\n\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n targetUris = fileContent\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#')); // Skip empty lines and comments\n\n if (targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n } else if (options.transport) {\n targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];\n targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n } else if (options.package) {\n targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];\n targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n } else {\n targetUris = [options.object!];\n targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n }\n\n // Step 1: Get ATC customizing to find default check variant\n ctx.logger.info('📋 Reading ATC customizing...');\n const customizing = await client.adt.atc.customizing.get();\n\n // Find systemCheckVariant in properties\n let checkVariant = options.variant;\n if (!checkVariant) {\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n if (variantProp?.value) {\n checkVariant = variantProp.value;\n } else {\n checkVariant = 'DEFAULT';\n }\n }\n ctx.logger.info(`🎯 Check variant: ${checkVariant}`);\n\n // Step 2: Create worklist (just with checkVariant, no objects)\n ctx.logger.info('📋 Creating ATC worklist...');\n const worklistCreateResponse = await client.adt.atc.worklists.create({\n checkVariant,\n });\n\n // Extract worklist ID - SAP can return plain string or object\n let worklistId: string;\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = worklistCreateResponse.match(/id=\"([^\"]+)\"/);\n worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();\n } else if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n worklistId = worklistCreateResponse.worklist.id;\n } else if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n worklistId = worklistCreateResponse.worklistRun.worklistId;\n } else {\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n }\n ctx.logger.info(`📋 Worklist created: ${worklistId}`);\n\n // Step 3: Run ATC checks with objects\n ctx.logger.info('⏳ Running ATC analysis...');\n const maxResults = Number.parseInt(options.maxResults || '100', 10);\n const runData = {\n run: {\n maximumVerdicts: maxResults,\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n await client.adt.atc.runs.post({ worklistId }, runData);\n\n // Step 4: Get results\n ctx.logger.info('📊 Fetching results...');\n const worklistResult = await client.adt.atc.worklists.get(worklistId, {\n includeExemptedFindings: 'false',\n });\n\n // Convert to AtcResult\n const result = convertWorklistToResult(\n worklistResult as WorklistResponse,\n checkVariant,\n );\n\n // Load finding resolver if requested\n let resolver: import('../types').FindingResolver | undefined;\n if (options.resolver) {\n const name = options.resolver;\n if (name === 'abapgit') {\n // Built-in abapgit resolver — no external dependencies\n const { createAbapGitResolver } = await import('../resolvers/abapgit');\n resolver = createAbapGitResolver();\n } else {\n // External resolver — try dynamic import\n try {\n ctx.logger.info(`📂 Loading external resolver: ${name}`);\n const mod = await import(name);\n if (typeof mod.createFindingResolver === 'function') {\n resolver = mod.createFindingResolver();\n } else {\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n }\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n }\n }\n\n // Output based on format\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'gitlab' && options.output) {\n await outputGitLabCodeQuality(result, options.output, { resolver });\n } else if (options.format === 'sarif' && options.output) {\n await outputSarifReport(result, options.output, targetName);\n } else {\n displayAtcResults(result, ctx.adtSystemName);\n }\n\n // Exit with error code if there are errors\n if (result.errorCount > 0) {\n process.exit(1);\n }\n },\n};\n\nexport default atcCommand;\n"],"mappings":"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"}
|