@codacy/analysis-runner 0.23.1 → 0.25.0

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +492 -12
  2. package/dist/index.js +1503 -256
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -30,15 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ DEFAULT_IMAGE_SCAN_PKG_TYPES: () => DEFAULT_IMAGE_SCAN_PKG_TYPES,
34
+ DEFAULT_IMAGE_SCAN_SEVERITIES: () => DEFAULT_IMAGE_SCAN_SEVERITIES,
33
35
  analyze: () => analyze,
34
36
  applyCodacyYamlExcludes: () => applyCodacyYamlExcludes,
35
37
  applyConfigOperation: () => applyConfigOperation,
38
+ buildCodacyrc: () => buildCodacyrc,
39
+ buildPatternTranslator: () => buildPatternTranslator,
36
40
  buildPayloads: () => buildPayloads,
37
41
  buildToolConfig: () => buildToolConfig,
38
42
  clearAdapters: () => clearAdapters,
39
43
  computePatternSummary: () => computePatternSummary,
40
44
  configureApiToken: () => configureApiToken,
41
- configureProxy: () => import_tooling8.configureProxy,
45
+ configureProxy: () => import_tooling10.configureProxy,
42
46
  createCodacyConfig: () => createCodacyConfig,
43
47
  createLogger: () => createLogger,
44
48
  createNullLogger: () => createNullLogger,
@@ -57,6 +61,7 @@ __export(index_exports, {
57
61
  initDefaultConfig: () => initDefaultConfig,
58
62
  initLocalConfig: () => initLocalConfig,
59
63
  initRemoteConfig: () => initRemoteConfig,
64
+ installTools: () => installTools,
60
65
  intersectConfigs: () => intersectConfigs,
61
66
  listDiffFiles: () => listDiffFiles,
62
67
  listIgnoredFiles: () => listIgnoredFiles,
@@ -65,29 +70,39 @@ __export(index_exports, {
65
70
  loadCredentials: () => loadCredentials,
66
71
  mergeConfigs: () => mergeConfigs,
67
72
  parseAutoFilters: () => parseAutoFilters,
73
+ parseCodacyrc: () => parseCodacyrc,
68
74
  parseReport: () => parseReport,
75
+ partitionContainerErrors: () => partitionContainerErrors,
69
76
  pruneEmptyTools: () => pruneEmptyTools,
70
77
  readBaselineConfig: () => readBaselineConfig,
71
78
  readCodacyConfig: () => readCodacyConfig,
72
79
  readCodacyYaml: () => readCodacyYaml,
80
+ readCodacyrc: () => readCodacyrc,
73
81
  registerAdapter: () => registerAdapter,
74
82
  registerLazyAdapter: () => registerLazyAdapter,
75
83
  registerUnsupportedTool: () => registerUnsupportedTool,
84
+ registryLookup: () => registryLookup,
76
85
  relativizeLocalConfigPaths: () => relativizeLocalConfigPaths,
77
86
  resolveApiToken: () => resolveApiToken,
78
87
  resolveBaselinePath: () => resolveBaselinePath,
88
+ resolveCodacyrc: () => resolveCodacyrc,
79
89
  resolveConfigPath: () => resolveConfigPath,
80
90
  resolveDiscoveryExcludes: () => resolveDiscoveryExcludes,
81
91
  resolveEngineToAdapter: () => resolveEngineToAdapter,
82
92
  resolveHeadCommit: () => resolveHeadCommit,
83
93
  resolveLocalConfigPaths: () => resolveLocalConfigPaths,
84
- resolveProxyConfig: () => import_tooling8.resolveProxyConfig,
94
+ resolveProxyConfig: () => import_tooling10.resolveProxyConfig,
85
95
  resolveRemoteAuth: () => resolveRemoteAuth,
86
96
  resolveRepositoryRoot: () => resolveRepositoryRoot,
87
97
  resolveResultsApiBaseUrl: () => resolveResultsApiBaseUrl,
98
+ resolveToolName: () => resolveToolName,
88
99
  rotateLogFiles: () => rotateLogFiles,
89
100
  saveCredentials: () => saveCredentials,
101
+ scanContainerImage: () => scanContainerImage,
102
+ serializeCodacyrc: () => serializeCodacyrc,
103
+ toEnginePatternId: () => toEnginePatternId,
90
104
  updateConfigIncremental: () => updateConfigIncremental,
105
+ uploadImageSbom: () => uploadImageSbom2,
91
106
  uploadReport: () => uploadReport,
92
107
  validateApiToken: () => validateApiToken,
93
108
  writeBaselineConfig: () => writeBaselineConfig,
@@ -98,7 +113,7 @@ module.exports = __toCommonJS(index_exports);
98
113
 
99
114
  // src/analyze.ts
100
115
  var import_os2 = __toESM(require("os"));
101
- var import_path4 = __toESM(require("path"));
116
+ var import_path5 = __toESM(require("path"));
102
117
  var import_fs3 = __toESM(require("fs"));
103
118
  var import_child_process3 = require("child_process");
104
119
  var import_util = require("util");
@@ -348,7 +363,7 @@ function createCodacyConfig(params = {}) {
348
363
  tools: params.tools ?? [],
349
364
  exclude: []
350
365
  };
351
- if (params.includeFiles && params.includeFiles.length > 0) {
366
+ if (params.includeFiles !== void 0) {
352
367
  config.includeFiles = params.includeFiles;
353
368
  }
354
369
  return config;
@@ -602,6 +617,11 @@ function listRepoFiles(repositoryRoot, exclude) {
602
617
  encoding: "utf-8",
603
618
  maxBuffer: 50 * 1024 * 1024
604
619
  }).trim();
620
+ const deleted = (0, import_child_process2.execSync)("git ls-files --deleted", {
621
+ cwd: repositoryRoot,
622
+ encoding: "utf-8",
623
+ maxBuffer: 50 * 1024 * 1024
624
+ }).trim();
605
625
  const files = /* @__PURE__ */ new Set();
606
626
  for (const f of tracked.split("\n")) {
607
627
  if (f) files.add(f);
@@ -609,10 +629,15 @@ function listRepoFiles(repositoryRoot, exclude) {
609
629
  for (const f of untracked.split("\n")) {
610
630
  if (f) files.add(f);
611
631
  }
632
+ for (const f of deleted.split("\n")) {
633
+ if (f) files.delete(f);
634
+ }
612
635
  const all = Array.from(files).sort();
613
636
  return exclude && exclude.length > 0 ? applyExclusions(all, [...exclude], []) : all;
614
- } catch {
615
- return [];
637
+ } catch (err) {
638
+ throw new Error(
639
+ `Could not list repository files in ${repositoryRoot}: ${err instanceof Error ? err.message : String(err)}`
640
+ );
616
641
  }
617
642
  }
618
643
  function filterOversizedFiles(files, repositoryRoot, logger) {
@@ -652,6 +677,22 @@ function filterOversizedFiles(files, repositoryRoot, logger) {
652
677
  }
653
678
  return { files: result, skipped };
654
679
  }
680
+ function filterSkippedFilesToTools(skipped, tools, globalExclude = []) {
681
+ if (skipped.length === 0 || tools.length === 0) return [];
682
+ const afterGlobal = applyExclusions(
683
+ skipped.map((s) => s.filePath),
684
+ [...globalExclude],
685
+ []
686
+ );
687
+ if (afterGlobal.length === 0) return [];
688
+ const routable = /* @__PURE__ */ new Set();
689
+ for (const { adapter, toolConfig } of tools) {
690
+ const toolExclude = toolConfig.exclude ?? [];
691
+ const visible = toolExclude.length > 0 ? applyExclusions(afterGlobal, [], [...toolExclude]) : afterGlobal;
692
+ for (const file of routeFilesToTool(visible, adapter, toolConfig)) routable.add(file);
693
+ }
694
+ return skipped.filter((s) => routable.has(s.filePath));
695
+ }
655
696
  function applyExclusions(files, globalExclude, toolExclude) {
656
697
  if (globalExclude.length === 0 && toolExclude.length === 0) return files;
657
698
  const matcher = new CompiledMatcher([...globalExclude, ...toolExclude]);
@@ -683,6 +724,211 @@ function filterByTargetFiles(allFiles, targetPatterns) {
683
724
  );
684
725
  }
685
726
 
727
+ // src/codacyrc.ts
728
+ var import_promises2 = __toESM(require("fs/promises"));
729
+
730
+ // src/pattern-id.ts
731
+ function toEnginePatternId(patternId, toolId) {
732
+ const prefix = `${toolId}_`;
733
+ return patternId.startsWith(prefix) ? patternId.slice(prefix.length) : patternId;
734
+ }
735
+ function buildPatternTranslator(toolId, patternsMetadata) {
736
+ const prefix = `${toolId}_`;
737
+ const byEngineId = /* @__PURE__ */ new Map();
738
+ let prefixedCount = 0;
739
+ let total = 0;
740
+ for (const entry of patternsMetadata ?? []) {
741
+ if (typeof entry?.id !== "string" || entry.id === "") continue;
742
+ total++;
743
+ byEngineId.set(entry.id, entry.id);
744
+ const unprefixed = toEnginePatternId(entry.id, toolId);
745
+ if (unprefixed !== entry.id) {
746
+ byEngineId.set(unprefixed, entry.id);
747
+ prefixedCount++;
748
+ }
749
+ }
750
+ const hasCatalogue = total > 0;
751
+ const toolIsPrefixed = hasCatalogue ? prefixedCount === total : true;
752
+ return {
753
+ toolId,
754
+ hasCatalogue,
755
+ isKnown: (enginePatternId) => !hasCatalogue || byEngineId.has(enginePatternId),
756
+ toInternal(enginePatternId) {
757
+ const known = byEngineId.get(enginePatternId);
758
+ if (known) return known;
759
+ if (!toolIsPrefixed) return enginePatternId;
760
+ return enginePatternId.startsWith(prefix) ? enginePatternId : prefix + enginePatternId;
761
+ }
762
+ };
763
+ }
764
+
765
+ // src/codacyrc.ts
766
+ function registryLookup() {
767
+ return { descriptors: getRegisteredDescriptors(), load: getAdapter };
768
+ }
769
+ function parseCodacyrc(raw, label = ".codacyrc") {
770
+ let parsed;
771
+ try {
772
+ parsed = JSON.parse(raw);
773
+ } catch {
774
+ try {
775
+ parsed = JSON.parse(Buffer.from(raw.trim(), "base64").toString("utf-8"));
776
+ } catch {
777
+ throw new Error(`Failed to parse ${label}: not valid JSON or base64-encoded JSON`);
778
+ }
779
+ }
780
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
781
+ throw new Error(`Failed to parse ${label}: expected a JSON object`);
782
+ }
783
+ return parsed;
784
+ }
785
+ async function readCodacyrc(filePath) {
786
+ let raw;
787
+ try {
788
+ raw = await import_promises2.default.readFile(filePath, "utf-8");
789
+ } catch (err) {
790
+ if (err?.code === "ENOENT") return null;
791
+ throw err;
792
+ }
793
+ return parseCodacyrc(raw, filePath);
794
+ }
795
+ function resolveToolName(name, descriptors) {
796
+ const runnable = descriptors.filter((d) => !d.unsupported);
797
+ const lower = name.toLowerCase();
798
+ return runnable.find((d) => d.shortName === name) ?? runnable.find((d) => d.shortName?.toLowerCase() === lower) ?? runnable.find((d) => d.id === name) ?? runnable.find((d) => d.id.toLowerCase() === lower);
799
+ }
800
+ function toParameterRecord(parameters) {
801
+ const out = {};
802
+ if (!Array.isArray(parameters)) return out;
803
+ for (const parameter of parameters) {
804
+ if (!parameter || typeof parameter.name !== "string" || parameter.name === "") continue;
805
+ const { value } = parameter;
806
+ if (value === null || value === void 0) continue;
807
+ out[parameter.name] = typeof value === "object" ? JSON.stringify(value) : String(value);
808
+ }
809
+ return out;
810
+ }
811
+ function withSpecDefaults(entry, configured) {
812
+ const defaults = {};
813
+ for (const spec of entry?.parameters ?? []) {
814
+ if (typeof spec?.name !== "string" || spec.name === "") continue;
815
+ if (spec.default === void 0 || spec.default === null) continue;
816
+ defaults[spec.name] = String(spec.default);
817
+ }
818
+ return { ...defaults, ...configured };
819
+ }
820
+ async function resolveCodacyrc(codacyrc, opts = {}) {
821
+ const lookup = opts.lookup ?? registryLookup();
822
+ const logger = opts.logger;
823
+ const allowed = opts.toolIds && opts.toolIds.length > 0 ? new Set(opts.toolIds.map((id) => id.toLowerCase())) : void 0;
824
+ const descriptors = allowed ? lookup.descriptors.filter((d) => allowed.has(d.id.toLowerCase())) : lookup.descriptors;
825
+ const tools = [];
826
+ const matchedToolIds = [];
827
+ const unmatchedToolNames = [];
828
+ const unknownPatterns = [];
829
+ for (const entry of codacyrc?.tools ?? []) {
830
+ if (typeof entry !== "object" || entry === null || typeof entry.name !== "string") continue;
831
+ const descriptor = resolveToolName(entry.name, descriptors);
832
+ if (!descriptor) {
833
+ unmatchedToolNames.push(entry.name);
834
+ continue;
835
+ }
836
+ if (matchedToolIds.includes(descriptor.id)) continue;
837
+ const adapter = await lookup.load(descriptor.id);
838
+ const translator = buildPatternTranslator(descriptor.id, adapter?.patternsMetadata);
839
+ const byId = new Map((adapter?.patternsMetadata ?? []).map((p) => [p.id, p]));
840
+ const patterns = [];
841
+ for (const raw of entry.patterns ?? []) {
842
+ if (typeof raw !== "object" || raw === null || typeof raw.patternId !== "string") continue;
843
+ const translated = translator.toInternal(raw.patternId);
844
+ if (!translator.isKnown(raw.patternId)) {
845
+ unknownPatterns.push({ toolId: descriptor.id, patternId: raw.patternId });
846
+ }
847
+ const parameters = withSpecDefaults(byId.get(translated), toParameterRecord(raw.parameters));
848
+ patterns.push({
849
+ patternId: translated,
850
+ ...Object.keys(parameters).length > 0 ? { parameters } : {}
851
+ });
852
+ }
853
+ tools.push({ toolId: descriptor.id, patterns });
854
+ matchedToolIds.push(descriptor.id);
855
+ }
856
+ if (unmatchedToolNames.length > 0) {
857
+ logger?.warn(
858
+ `.codacyrc names ${unmatchedToolNames.length} tool(s) with no local analyzer here: ${unmatchedToolNames.join(", ")}`
859
+ );
860
+ }
861
+ for (const unknown of unknownPatterns) {
862
+ logger?.warn(
863
+ `.codacyrc enables "${unknown.patternId}", which ${unknown.toolId} does not ship; passing it through`
864
+ );
865
+ }
866
+ const files = Array.isArray(codacyrc?.files) ? codacyrc.files.filter((f) => typeof f === "string") : void 0;
867
+ const config = createCodacyConfig({ source: "container", tools, includeFiles: files });
868
+ return {
869
+ config,
870
+ matchedToolIds,
871
+ unmatchedToolNames,
872
+ unknownPatterns,
873
+ options: codacyrc?.options ?? {}
874
+ };
875
+ }
876
+ function toWireValue(raw, spec) {
877
+ const declared = spec?.default;
878
+ if (typeof declared === "number") {
879
+ const parsed = Number(raw);
880
+ return raw.trim() !== "" && Number.isFinite(parsed) ? parsed : raw;
881
+ }
882
+ if (typeof declared === "boolean") {
883
+ if (raw === "true") return true;
884
+ if (raw === "false") return false;
885
+ return raw;
886
+ }
887
+ if (declared !== null && typeof declared === "object") {
888
+ try {
889
+ return JSON.parse(raw);
890
+ } catch {
891
+ return raw;
892
+ }
893
+ }
894
+ return raw;
895
+ }
896
+ function toParameterList(entry, configured) {
897
+ const merged = withSpecDefaults(entry, configured ?? {});
898
+ const names = Object.keys(merged).sort();
899
+ if (names.length === 0) return void 0;
900
+ const specByName = /* @__PURE__ */ new Map();
901
+ for (const spec of entry?.parameters ?? []) {
902
+ if (typeof spec?.name === "string" && spec.name !== "") specByName.set(spec.name, spec);
903
+ }
904
+ return names.map((name) => ({ name, value: toWireValue(merged[name], specByName.get(name)) }));
905
+ }
906
+ function buildCodacyrc(tools, opts = {}) {
907
+ const files = /* @__PURE__ */ new Set();
908
+ const wireTools = [];
909
+ for (const { adapter, toolConfig, files: routed } of tools) {
910
+ for (const file of routed) files.add(file);
911
+ if (!adapter.shortName) {
912
+ opts.logger?.warn(
913
+ `${adapter.id} declares no Codacy short-name; emitting "${adapter.id}" as tools[].name, which a Codacy engine image will not recognize`
914
+ );
915
+ }
916
+ const byId = new Map((adapter.patternsMetadata ?? []).map((p) => [p.id, p]));
917
+ const patterns = toolConfig.useLocalConfigurationFile ? [] : (toolConfig.patterns ?? []).map((pattern) => {
918
+ const parameters = toParameterList(byId.get(pattern.patternId), pattern.parameters);
919
+ return {
920
+ patternId: toEnginePatternId(pattern.patternId, adapter.id),
921
+ ...parameters ? { parameters } : {}
922
+ };
923
+ });
924
+ wireTools.push({ name: adapter.shortName ?? adapter.id, patterns });
925
+ }
926
+ return { files: [...files].sort(), tools: wireTools };
927
+ }
928
+ function serializeCodacyrc(codacyrc) {
929
+ return JSON.stringify(codacyrc, null, 2) + "\n";
930
+ }
931
+
686
932
  // src/executor.ts
687
933
  var import_tooling2 = require("@codacy/tooling");
688
934
  var HARD_TIMEOUT_GRACE_MS = 2e3;
@@ -1081,6 +1327,81 @@ function computePatternSummary(issues, lookup) {
1081
1327
  };
1082
1328
  }
1083
1329
 
1330
+ // src/tool-install.ts
1331
+ var import_path4 = __toESM(require("path"));
1332
+ function makeToolContext(base, adapter, targetFiles, generatedRoot) {
1333
+ return {
1334
+ ...base,
1335
+ targetFiles,
1336
+ toolInstallDir: import_path4.default.join(base.globalDir, "tools", adapter.id),
1337
+ generatedConfigDir: import_path4.default.join(
1338
+ generatedRoot ?? import_path4.default.join(base.repositoryRoot, ".codacy", "generated"),
1339
+ adapter.id
1340
+ )
1341
+ };
1342
+ }
1343
+ async function installMissingTool(adapter, ctx, logger) {
1344
+ logger.info(`${adapter.displayName} is missing, attempting install...`);
1345
+ try {
1346
+ logger.info(`Downloading ${adapter.displayName}...`);
1347
+ const installResult = await adapter.install(ctx);
1348
+ if (installResult.success) {
1349
+ logger.info(`Checking ${adapter.displayName} installation...`);
1350
+ const recheck = await adapter.checkAvailability(ctx);
1351
+ if (recheck.available) {
1352
+ logger.info(`${adapter.displayName} is now available`);
1353
+ return {
1354
+ kind: "installed",
1355
+ version: recheck.version ?? installResult.installedVersion ?? adapter.preferredVersion,
1356
+ installation: recheck.installation ?? "codacy"
1357
+ };
1358
+ }
1359
+ logger.error(
1360
+ `${adapter.displayName} post-install check failed: ${recheck.reason ?? "unknown"}`
1361
+ );
1362
+ return {
1363
+ kind: "failed",
1364
+ error: {
1365
+ toolId: adapter.id,
1366
+ phase: "toolInstall",
1367
+ kind: "PostInstallCheckFailed",
1368
+ message: `Installed ${adapter.id} but availability check still fails: ${recheck.reason ?? "unknown"}`,
1369
+ level: "error"
1370
+ }
1371
+ };
1372
+ }
1373
+ if (installResult.error?.kind === "NotSupported") {
1374
+ logger.info(`${adapter.displayName} does not support automated install, skipping.`);
1375
+ return { kind: "notSupported" };
1376
+ }
1377
+ const errMsg = installResult.error?.message ?? `Failed to install ${adapter.id}`;
1378
+ logger.error(`${adapter.displayName} install failed: ${errMsg}`);
1379
+ return {
1380
+ kind: "failed",
1381
+ error: installResult.error ?? {
1382
+ toolId: adapter.id,
1383
+ phase: "toolInstall",
1384
+ kind: "InstallFailed",
1385
+ message: errMsg,
1386
+ level: "error"
1387
+ }
1388
+ };
1389
+ } catch (err) {
1390
+ const errMsg = err instanceof Error ? err.message : String(err);
1391
+ logger.error(`${adapter.displayName} install failed: ${errMsg}`);
1392
+ return {
1393
+ kind: "failed",
1394
+ error: {
1395
+ toolId: adapter.id,
1396
+ phase: "toolInstall",
1397
+ kind: "InstallError",
1398
+ message: `Install failed for ${adapter.id}: ${errMsg}`,
1399
+ level: "error"
1400
+ }
1401
+ };
1402
+ }
1403
+ }
1404
+
1084
1405
  // src/analyze.ts
1085
1406
  var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
1086
1407
  var CHECK_CONCURRENCY = 12;
@@ -1111,7 +1432,7 @@ async function analyze(options) {
1111
1432
  toolExecutionMs: 0,
1112
1433
  installMs: 0
1113
1434
  };
1114
- const emptyCapability = {
1435
+ const emptyCapability2 = {
1115
1436
  ready: [],
1116
1437
  unavailable: [],
1117
1438
  checkedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -1122,7 +1443,7 @@ async function analyze(options) {
1122
1443
  startedAt,
1123
1444
  options.repositoryRoot,
1124
1445
  mode,
1125
- emptyCapability,
1446
+ emptyCapability2,
1126
1447
  [],
1127
1448
  [
1128
1449
  {
@@ -1142,7 +1463,7 @@ async function analyze(options) {
1142
1463
  startedAt,
1143
1464
  options.repositoryRoot,
1144
1465
  mode,
1145
- emptyCapability,
1466
+ emptyCapability2,
1146
1467
  [],
1147
1468
  [
1148
1469
  {
@@ -1156,34 +1477,42 @@ async function analyze(options) {
1156
1477
  []
1157
1478
  );
1158
1479
  }
1480
+ let codacyConfig;
1481
+ let configError;
1159
1482
  try {
1160
- await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
1161
- cwd: options.repositoryRoot
1162
- });
1483
+ codacyConfig = options.config ?? await readCodacyConfig(options.repositoryRoot, options.configFile);
1163
1484
  } catch (err) {
1164
- const isGitMissing = err instanceof Error && "code" in err && err.code === "ENOENT";
1165
- const message = isGitMissing ? "git is not installed. Codacy analysis requires git for file discovery." : `${options.repositoryRoot} is not a git repository. Codacy analysis requires a git repository for file discovery. Run \`git init\` to initialize one.`;
1166
- const kind = isGitMissing ? "GitNotInstalled" : "NotAGitRepository";
1167
- return buildResult(
1168
- startedAt,
1169
- options.repositoryRoot,
1170
- mode,
1171
- emptyCapability,
1172
- [],
1173
- [{ toolId: "runner", phase: "requirementCheck", kind, message, level: "error" }],
1174
- []
1175
- );
1485
+ codacyConfig = null;
1486
+ configError = err;
1487
+ }
1488
+ if (configError === void 0 && codacyConfig?.includeFiles === void 0) {
1489
+ try {
1490
+ await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
1491
+ cwd: options.repositoryRoot
1492
+ });
1493
+ } catch (err) {
1494
+ const isGitMissing = err instanceof Error && "code" in err && err.code === "ENOENT";
1495
+ const message = isGitMissing ? "git is not installed. Codacy analysis requires git for file discovery." : `${options.repositoryRoot} is not a git repository. Codacy analysis requires a git repository for file discovery. Run \`git init\` to initialize one.`;
1496
+ const kind = isGitMissing ? "GitNotInstalled" : "NotAGitRepository";
1497
+ return buildResult(
1498
+ startedAt,
1499
+ options.repositoryRoot,
1500
+ mode,
1501
+ emptyCapability2,
1502
+ [],
1503
+ [{ toolId: "runner", phase: "requirementCheck", kind, message, level: "error" }],
1504
+ []
1505
+ );
1506
+ }
1176
1507
  }
1177
1508
  const configLabel = options.configFile ?? ".codacy/codacy.config.json";
1178
- let codacyConfig;
1179
- try {
1180
- codacyConfig = await readCodacyConfig(options.repositoryRoot, options.configFile);
1181
- } catch (err) {
1509
+ if (configError !== void 0) {
1510
+ const err = configError;
1182
1511
  return buildResult(
1183
1512
  startedAt,
1184
1513
  options.repositoryRoot,
1185
1514
  mode,
1186
- emptyCapability,
1515
+ emptyCapability2,
1187
1516
  [],
1188
1517
  [
1189
1518
  {
@@ -1202,7 +1531,7 @@ async function analyze(options) {
1202
1531
  startedAt,
1203
1532
  options.repositoryRoot,
1204
1533
  mode,
1205
- emptyCapability,
1534
+ emptyCapability2,
1206
1535
  [],
1207
1536
  [
1208
1537
  {
@@ -1219,7 +1548,7 @@ async function analyze(options) {
1219
1548
  const requestedTools = options.tools ?? codacyConfig.tools.map((t) => t.toolId);
1220
1549
  const adapters = await getRegisteredAdapters(requestedTools);
1221
1550
  logger.info("Pipeline started", { mode, toolCount: adapters.length, parallelTools, toolTimeout });
1222
- const globalDir = import_path4.default.join(import_os2.default.homedir(), ".codacy");
1551
+ const globalDir = (0, import_tooling3.resolveGlobalDir)();
1223
1552
  const runnerEnv = options.runner ?? "local";
1224
1553
  const baseCtx = {
1225
1554
  repositoryRoot: options.repositoryRoot,
@@ -1266,7 +1595,7 @@ async function analyze(options) {
1266
1595
  displayName: adapter.displayName,
1267
1596
  version: availability.version ?? adapter.preferredVersion,
1268
1597
  installation: availability.installation ?? "global",
1269
- runner: "local"
1598
+ runner: runnerEnv
1270
1599
  }
1271
1600
  };
1272
1601
  }
@@ -1366,6 +1695,25 @@ async function analyze(options) {
1366
1695
  logger.info(`Updated ${updatedCount} managed tool(s) to their pinned versions`);
1367
1696
  }
1368
1697
  }
1698
+ if (mode === "auto-install" && runnerEnv === "container") {
1699
+ return buildResult(
1700
+ startedAt,
1701
+ options.repositoryRoot,
1702
+ mode,
1703
+ capability,
1704
+ [],
1705
+ [
1706
+ {
1707
+ toolId: "runner",
1708
+ phase: "requirementCheck",
1709
+ kind: "InstallNotAllowed",
1710
+ message: "Installs are disabled under the container runner; the image must ship its tools.",
1711
+ level: "error"
1712
+ }
1713
+ ],
1714
+ []
1715
+ );
1716
+ }
1369
1717
  if (mode === "auto-install" && capability.unavailable.length > 0) {
1370
1718
  const installStartMs = Date.now();
1371
1719
  const installErrors = [];
@@ -1373,62 +1721,24 @@ async function analyze(options) {
1373
1721
  const status = capability.unavailable[i];
1374
1722
  const adapter = adapters.find((a) => a.id === status.toolId);
1375
1723
  if (!adapter) continue;
1376
- const ctx = makeToolContext(baseCtx, adapter, []);
1377
- logger.info(`${adapter.displayName} is missing, attempting install...`);
1378
- try {
1379
- logger.info(`Downloading ${adapter.displayName}...`);
1380
- const installResult = await adapter.install(ctx);
1381
- if (installResult.success) {
1382
- logger.info(`Checking ${adapter.displayName} installation...`);
1383
- const recheck = await adapter.checkAvailability(ctx);
1384
- if (recheck.available) {
1385
- logger.info(`${adapter.displayName} is now available`);
1386
- capability.ready.push({
1387
- toolId: adapter.id,
1388
- displayName: adapter.displayName,
1389
- version: recheck.version ?? installResult.installedVersion ?? adapter.preferredVersion,
1390
- installation: recheck.installation ?? "codacy",
1391
- runner: "local"
1392
- });
1393
- capability.unavailable.splice(i, 1);
1394
- } else {
1395
- logger.error(
1396
- `${adapter.displayName} post-install check failed: ${recheck.reason ?? "unknown"}`
1397
- );
1398
- installErrors.push({
1399
- toolId: adapter.id,
1400
- phase: "toolInstall",
1401
- kind: "PostInstallCheckFailed",
1402
- message: `Installed ${adapter.id} but availability check still fails: ${recheck.reason ?? "unknown"}`,
1403
- level: "error"
1404
- });
1405
- }
1406
- } else if (installResult.error?.kind === "NotSupported") {
1407
- logger.info(`${adapter.displayName} does not support automated install, skipping.`);
1408
- capability.unavailable.splice(i, 1);
1409
- } else {
1410
- const errMsg = installResult.error?.message ?? `Failed to install ${adapter.id}`;
1411
- logger.error(`${adapter.displayName} install failed: ${errMsg}`);
1412
- installErrors.push(
1413
- installResult.error ?? {
1414
- toolId: adapter.id,
1415
- phase: "toolInstall",
1416
- kind: "InstallFailed",
1417
- message: errMsg,
1418
- level: "error"
1419
- }
1420
- );
1421
- }
1422
- } catch (err) {
1423
- const errMsg = err instanceof Error ? err.message : String(err);
1424
- logger.error(`${adapter.displayName} install failed: ${errMsg}`);
1425
- installErrors.push({
1724
+ const outcome = await installMissingTool(
1725
+ adapter,
1726
+ makeToolContext(baseCtx, adapter, []),
1727
+ logger
1728
+ );
1729
+ if (outcome.kind === "installed") {
1730
+ capability.ready.push({
1426
1731
  toolId: adapter.id,
1427
- phase: "toolInstall",
1428
- kind: "InstallError",
1429
- message: `Install failed for ${adapter.id}: ${errMsg}`,
1430
- level: "error"
1732
+ displayName: adapter.displayName,
1733
+ version: outcome.version,
1734
+ installation: outcome.installation,
1735
+ runner: runnerEnv
1431
1736
  });
1737
+ capability.unavailable.splice(i, 1);
1738
+ } else if (outcome.kind === "notSupported") {
1739
+ capability.unavailable.splice(i, 1);
1740
+ } else {
1741
+ installErrors.push(outcome.error);
1432
1742
  }
1433
1743
  }
1434
1744
  phaseTimings.installMs = Date.now() - installStartMs;
@@ -1497,8 +1807,59 @@ async function analyze(options) {
1497
1807
  }
1498
1808
  const fileDiscoveryStartMs = Date.now();
1499
1809
  const globalExclude = codacyConfig.exclude || [];
1500
- let allFiles = listRepoFiles(options.repositoryRoot);
1501
- logger.debug("File discovery", { totalFiles: allFiles.length });
1810
+ let allFiles;
1811
+ const runnerErrors = [];
1812
+ if (codacyConfig.includeFiles === void 0) {
1813
+ try {
1814
+ allFiles = listRepoFiles(options.repositoryRoot);
1815
+ } catch (err) {
1816
+ return buildResult(
1817
+ startedAt,
1818
+ options.repositoryRoot,
1819
+ mode,
1820
+ capability,
1821
+ [],
1822
+ [
1823
+ {
1824
+ toolId: "runner",
1825
+ phase: "requirementCheck",
1826
+ kind: "FileDiscoveryFailed",
1827
+ message: err instanceof Error ? err.message : String(err),
1828
+ level: "error"
1829
+ }
1830
+ ],
1831
+ []
1832
+ );
1833
+ }
1834
+ logger.debug("File discovery", { source: "git", totalFiles: allFiles.length });
1835
+ } else {
1836
+ const seen = /* @__PURE__ */ new Set();
1837
+ allFiles = [];
1838
+ for (const raw of codacyConfig.includeFiles) {
1839
+ const rel = raw.replace(/\\/g, "/").replace(/^\.\//, "");
1840
+ if (rel === "" || seen.has(rel)) continue;
1841
+ seen.add(rel);
1842
+ const absolute = import_path5.default.resolve(options.repositoryRoot, rel);
1843
+ const relativeToRoot = import_path5.default.relative(options.repositoryRoot, absolute);
1844
+ const escapesRoot = relativeToRoot === ".." || relativeToRoot.startsWith(`..${import_path5.default.sep}`) || import_path5.default.isAbsolute(relativeToRoot);
1845
+ const stat = escapesRoot ? void 0 : import_fs3.default.statSync(absolute, { throwIfNoEntry: false });
1846
+ if (!stat?.isFile()) {
1847
+ runnerErrors.push({
1848
+ toolId: "runner",
1849
+ phase: "requirementCheck",
1850
+ kind: "FileNotFound",
1851
+ message: escapesRoot ? `${rel} was requested for analysis but is outside the repository` : `${rel} was requested for analysis but does not exist`,
1852
+ level: "warning",
1853
+ filePath: rel,
1854
+ fileNotAnalyzed: true
1855
+ });
1856
+ continue;
1857
+ }
1858
+ allFiles.push(rel);
1859
+ }
1860
+ allFiles.sort();
1861
+ logger.debug("File discovery", { source: "config", totalFiles: allFiles.length });
1862
+ }
1502
1863
  if (options.gitScope) {
1503
1864
  const scopeFiles = resolveGitScope(options.repositoryRoot, options.gitScope);
1504
1865
  logger.debug("Git scope resolved", {
@@ -1507,7 +1868,7 @@ async function analyze(options) {
1507
1868
  });
1508
1869
  if (scopeFiles.length === 0) {
1509
1870
  logger.info("Git scope resolved to zero files, skipping analysis");
1510
- return buildResult(startedAt, options.repositoryRoot, mode, capability, [], [], []);
1871
+ return buildResult(startedAt, options.repositoryRoot, mode, capability, [], runnerErrors, []);
1511
1872
  }
1512
1873
  const scopeSet = new Set(scopeFiles);
1513
1874
  allFiles = allFiles.filter((f) => scopeSet.has(f));
@@ -1524,6 +1885,8 @@ async function analyze(options) {
1524
1885
  const fileRoutingStartMs = Date.now();
1525
1886
  const readyById = new Map(capability.ready.map((r) => [r.toolId, r]));
1526
1887
  const readyAdapters = adapters.filter((a) => readyById.has(a.id));
1888
+ const generatedRoot = resolveGeneratedRoot(options.repositoryRoot, runnerEnv, logger);
1889
+ const generatedRootIsTemporary = runnerEnv === "container";
1527
1890
  const filesAfterGlobalExclude = applyExclusions(allFiles, globalExclude, []);
1528
1891
  const toolRuns = [];
1529
1892
  const candidates = [];
@@ -1540,7 +1903,7 @@ async function analyze(options) {
1540
1903
  candidates.push({
1541
1904
  adapter,
1542
1905
  toolFiles,
1543
- configCtx: makeToolContext(baseCtx, adapter, toolFiles),
1906
+ configCtx: makeToolContext(baseCtx, adapter, toolFiles, generatedRoot),
1544
1907
  readyEntry
1545
1908
  });
1546
1909
  }
@@ -1588,6 +1951,32 @@ async function analyze(options) {
1588
1951
  toolRuns.push({ adapter, ctx: configCtx, codacyToolConfig });
1589
1952
  }
1590
1953
  phaseTimings.fileRoutingMs = Date.now() - fileRoutingStartMs;
1954
+ const analyzedSkippedFiles = filterSkippedFilesToTools(
1955
+ skippedFiles,
1956
+ toolRuns.map(({ adapter, codacyToolConfig }) => ({
1957
+ adapter,
1958
+ toolConfig: codacyToolConfig
1959
+ })),
1960
+ globalExclude
1961
+ );
1962
+ if (options.onCodacyrc && toolRuns.length > 0) {
1963
+ if (toolRuns.length > 1) {
1964
+ logger.info("Emitting .codacyrc for multiple tools", {
1965
+ toolCount: toolRuns.length,
1966
+ hint: "`files` is the union of their routed lists; use --tool to pin one"
1967
+ });
1968
+ }
1969
+ await options.onCodacyrc(
1970
+ buildCodacyrc(
1971
+ toolRuns.map(({ adapter, ctx, codacyToolConfig }) => ({
1972
+ adapter,
1973
+ toolConfig: codacyToolConfig,
1974
+ files: ctx.targetFiles
1975
+ })),
1976
+ { logger }
1977
+ )
1978
+ );
1979
+ }
1591
1980
  const toolExecStartMs = Date.now();
1592
1981
  logger.info("Starting tool execution", {
1593
1982
  toolCount: toolRuns.length,
@@ -1613,7 +2002,17 @@ async function analyze(options) {
1613
2002
  }
1614
2003
  phaseTimings.toolExecutionMs = Date.now() - toolExecStartMs;
1615
2004
  const allIssues = normalizeIssues(results.flatMap((r) => r.issues));
1616
- const allErrors = results.flatMap((r) => r.errors);
2005
+ if (generatedRootIsTemporary) {
2006
+ try {
2007
+ import_fs3.default.rmSync(generatedRoot, { recursive: true, force: true });
2008
+ } catch (err) {
2009
+ logger.debug("Could not remove the temporary generated-config directory", {
2010
+ path: generatedRoot,
2011
+ error: err instanceof Error ? err.message : String(err)
2012
+ });
2013
+ }
2014
+ }
2015
+ const allErrors = [...runnerErrors, ...results.flatMap((r) => r.errors)];
1617
2016
  const allSummaries = results.map((r) => r.summary);
1618
2017
  const patternLookup = /* @__PURE__ */ new Map();
1619
2018
  for (const { adapter } of toolRuns) {
@@ -1643,16 +2042,14 @@ async function analyze(options) {
1643
2042
  allSummaries,
1644
2043
  patternSummary,
1645
2044
  phaseTimings,
1646
- skippedFiles
2045
+ analyzedSkippedFiles
1647
2046
  );
1648
2047
  }
1649
- function makeToolContext(base, adapter, targetFiles) {
1650
- return {
1651
- ...base,
1652
- targetFiles,
1653
- toolInstallDir: import_path4.default.join(base.globalDir, "tools", adapter.id),
1654
- generatedConfigDir: import_path4.default.join(base.repositoryRoot, ".codacy", "generated", adapter.id)
1655
- };
2048
+ function resolveGeneratedRoot(repositoryRoot, runnerEnv, logger) {
2049
+ if (runnerEnv !== "container") return import_path5.default.join(repositoryRoot, ".codacy", "generated");
2050
+ const dir = import_fs3.default.mkdtempSync(import_path5.default.join(import_os2.default.tmpdir(), "codacy-generated-"));
2051
+ logger.info("Generated tool configs will be written outside the source directory", { path: dir });
2052
+ return dir;
1656
2053
  }
1657
2054
  async function updateStaleManagedTools(capability, adapters, baseCtx, logger) {
1658
2055
  const adaptersById = new Map(adapters.map((a) => [a.id, a]));
@@ -1662,7 +2059,7 @@ async function updateStaleManagedTools(capability, adapters, baseCtx, logger) {
1662
2059
  if (!adapter) continue;
1663
2060
  if (status.installation === "bundled") continue;
1664
2061
  if (status.version === adapter.preferredVersion) continue;
1665
- if (status.installation === "global") {
2062
+ if (status.installation === "global" || status.installation === "preinstalled") {
1666
2063
  status.expectedVersion = adapter.preferredVersion;
1667
2064
  logger.debug("System tool version differs from the pinned version", {
1668
2065
  toolId: adapter.id,
@@ -1758,6 +2155,8 @@ async function repairMissingManagedTools(capability, adapters, baseCtx, logger)
1758
2155
  displayName: adapter.displayName,
1759
2156
  version: recheck.version ?? installResult.installedVersion ?? adapter.preferredVersion,
1760
2157
  installation: recheck.installation ?? "codacy",
2158
+ // Always local: this repair pass is skipped entirely under the container
2159
+ // runner, which never installs anything.
1761
2160
  runner: "local"
1762
2161
  });
1763
2162
  capability.unavailable.splice(i, 1);
@@ -1807,9 +2206,81 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
1807
2206
  };
1808
2207
  }
1809
2208
 
1810
- // src/format.ts
1811
- var import_path5 = __toESM(require("path"));
2209
+ // src/install-tools.ts
2210
+ var import_os3 = __toESM(require("os"));
1812
2211
  var import_tooling4 = require("@codacy/tooling");
2212
+ function supportsThisPlatform(adapter) {
2213
+ return adapter.supportedOS.includes(import_os3.default.platform()) && adapter.supportedArch.includes(import_os3.default.arch());
2214
+ }
2215
+ async function installTools(options) {
2216
+ const logger = options.logger ?? createNullLogger();
2217
+ const unknown = [];
2218
+ let adapters;
2219
+ if (options.all) {
2220
+ adapters = await getRegisteredAdapters();
2221
+ } else {
2222
+ const requested = options.tools ?? [];
2223
+ for (const id of requested) {
2224
+ const match = getRegisteredDescriptors([id]);
2225
+ if (match.length === 0 || match[0].unsupported) unknown.push(id);
2226
+ }
2227
+ const known = requested.filter((id) => !unknown.includes(id));
2228
+ adapters = known.length > 0 ? await getRegisteredAdapters(known) : [];
2229
+ }
2230
+ const base = {
2231
+ repositoryRoot: process.cwd(),
2232
+ globalDir: (0, import_tooling4.resolveGlobalDir)(),
2233
+ platform: { os: import_os3.default.platform(), arch: import_os3.default.arch() },
2234
+ logger,
2235
+ runner: "local",
2236
+ ...options.onProgress ? { onProgress: options.onProgress } : {}
2237
+ };
2238
+ const tools = [];
2239
+ for (const adapter of adapters) {
2240
+ const report = {
2241
+ toolId: adapter.id,
2242
+ displayName: adapter.displayName,
2243
+ status: "failed"
2244
+ };
2245
+ tools.push(report);
2246
+ if (!supportsThisPlatform(adapter)) {
2247
+ report.status = "unsupportedPlatform";
2248
+ report.message = `${adapter.displayName} does not support ${import_os3.default.platform()}-${import_os3.default.arch()}`;
2249
+ continue;
2250
+ }
2251
+ const ctx = makeToolContext(base, adapter, []);
2252
+ try {
2253
+ const availability = await adapter.checkAvailability(ctx);
2254
+ if (availability.available) {
2255
+ report.status = "present";
2256
+ report.version = availability.version;
2257
+ report.installation = availability.installation;
2258
+ continue;
2259
+ }
2260
+ } catch (err) {
2261
+ logger.debug("Availability check threw before install", {
2262
+ toolId: adapter.id,
2263
+ error: err instanceof Error ? err.message : String(err)
2264
+ });
2265
+ }
2266
+ const outcome = await installMissingTool(adapter, ctx, logger);
2267
+ if (outcome.kind === "installed") {
2268
+ report.status = "installed";
2269
+ report.version = outcome.version;
2270
+ report.installation = outcome.installation;
2271
+ } else if (outcome.kind === "notSupported") {
2272
+ report.status = "notSupported";
2273
+ report.message = `${adapter.displayName} cannot be installed automatically; install it on PATH`;
2274
+ } else {
2275
+ report.message = outcome.error.message;
2276
+ }
2277
+ }
2278
+ return { tools, unknown };
2279
+ }
2280
+
2281
+ // src/format.ts
2282
+ var import_path6 = __toESM(require("path"));
2283
+ var import_tooling5 = require("@codacy/tooling");
1813
2284
  var import_ansis = __toESM(require("ansis"));
1814
2285
  var import_cli_table3 = __toESM(require("cli-table3"));
1815
2286
  var MAX_SKIPPED_FILES_SHOWN = 5;
@@ -1924,17 +2395,48 @@ function mapSeverityToSarif(severity) {
1924
2395
  return "warning";
1925
2396
  }
1926
2397
  }
2398
+ function partitionContainerErrors(errors) {
2399
+ const fileErrors = [];
2400
+ const otherErrors = [];
2401
+ for (const error of errors) {
2402
+ if (error.fileNotAnalyzed && error.filePath) fileErrors.push(error);
2403
+ else otherErrors.push(error);
2404
+ }
2405
+ return { fileErrors, otherErrors };
2406
+ }
1927
2407
  function formatContainer(result) {
1928
- return result.issues.map(
1929
- (issue) => JSON.stringify({
2408
+ const lines = [];
2409
+ const fileErrors = /* @__PURE__ */ new Map();
2410
+ for (const error of partitionContainerErrors(result.errors).fileErrors) {
2411
+ const filename = error.filePath;
2412
+ if (fileErrors.has(filename)) continue;
2413
+ fileErrors.set(filename, flattenErrorMessage(error.message) || void 0);
2414
+ }
2415
+ for (const skipped of result.skippedFiles ?? []) {
2416
+ if (fileErrors.has(skipped.filePath)) continue;
2417
+ fileErrors.set(
2418
+ skipped.filePath,
2419
+ `File skipped: ${skipped.sizeBytes} bytes exceeds the ${MAX_FILE_SIZE_BYTES}-byte limit`
2420
+ );
2421
+ }
2422
+ for (const issue of result.issues) {
2423
+ if (issue.line <= 0) {
2424
+ if (!fileErrors.has(issue.filePath)) {
2425
+ fileErrors.set(issue.filePath, import_tooling5.LINE_NUMBERS_NOT_SUPPORTED);
2426
+ }
2427
+ continue;
2428
+ }
2429
+ lines.push({
1930
2430
  filename: issue.filePath,
1931
- patternId: issue.patternId,
2431
+ patternId: toEnginePatternId(issue.patternId, issue.toolId),
1932
2432
  line: issue.line,
1933
2433
  message: issue.message,
1934
2434
  suggestion: issue.suggestion,
1935
2435
  sourceId: issue.sourceId
1936
- })
1937
- ).join("\n");
2436
+ });
2437
+ }
2438
+ for (const [filename, message] of fileErrors) lines.push({ filename, message });
2439
+ return lines.map((line) => JSON.stringify(line)).join("\n");
1938
2440
  }
1939
2441
  function formatText(result) {
1940
2442
  const lines = [];
@@ -2015,7 +2517,7 @@ function formatTextSummary(result, isInspect) {
2015
2517
  } else {
2016
2518
  const headerLine = `Analysis complete: ${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`;
2017
2519
  lines.push(totalIssues > 0 ? import_ansis.default.bold.red(headerLine) : import_ansis.default.bold(headerLine));
2018
- lines.push(`${toolCount} tool${toolCount !== 1 ? "s" : ""} ran in ${(0, import_tooling4.formatDuration)(duration)}`);
2520
+ lines.push(`${toolCount} tool${toolCount !== 1 ? "s" : ""} ran in ${(0, import_tooling5.formatDuration)(duration)}`);
2019
2521
  }
2020
2522
  if (totalErrors > 0) {
2021
2523
  lines.push(import_ansis.default.red(`${totalErrors} error${totalErrors !== 1 ? "s" : ""} occurred`));
@@ -2077,7 +2579,7 @@ function formatTextSummary(result, isInspect) {
2077
2579
  formatConfigCell(tool, result.metadata.repositoryRoot),
2078
2580
  filesCell,
2079
2581
  issueCount > 0 ? import_ansis.default.bold.red(String(issueCount)) : String(issueCount),
2080
- (0, import_tooling4.formatDuration)(durationMs)
2582
+ (0, import_tooling5.formatDuration)(durationMs)
2081
2583
  ]);
2082
2584
  }
2083
2585
  for (const tool of unavailableTools) {
@@ -2118,7 +2620,7 @@ function formatTextSummary(result, isInspect) {
2118
2620
  entries2.push(["Other", other]);
2119
2621
  }
2120
2622
  for (const [label, ms] of entries2) {
2121
- lines.push(` ${label.padEnd(22)} ${(0, import_tooling4.formatDuration)(ms)}`);
2623
+ lines.push(` ${label.padEnd(22)} ${(0, import_tooling5.formatDuration)(ms)}`);
2122
2624
  }
2123
2625
  }
2124
2626
  }
@@ -2235,7 +2737,7 @@ function formatMultilineIssue(output, issue) {
2235
2737
  }
2236
2738
  function formatInstallCell(tool) {
2237
2739
  if (!tool.expectedVersion) return tool.installation;
2238
- const mismatch = (0, import_tooling4.describeVersionMismatch)(tool.version, tool.expectedVersion);
2740
+ const mismatch = (0, import_tooling5.describeVersionMismatch)(tool.version, tool.expectedVersion);
2239
2741
  if (!mismatch) return tool.installation;
2240
2742
  return `${tool.installation} ${import_ansis.default.yellow(`(${mismatch})`)}`;
2241
2743
  }
@@ -2246,7 +2748,7 @@ function formatFilesCell(filesRouted) {
2246
2748
  }
2247
2749
  function formatConfigCell(tool, repositoryRoot) {
2248
2750
  if (tool.configSource === "local" && tool.configPath) {
2249
- const rawPath = import_path5.default.isAbsolute(tool.configPath) ? import_path5.default.relative(repositoryRoot, tool.configPath) : tool.configPath;
2751
+ const rawPath = import_path6.default.isAbsolute(tool.configPath) ? import_path6.default.relative(repositoryRoot, tool.configPath) : tool.configPath;
2250
2752
  return rawPath.startsWith("./") ? rawPath : `./${rawPath}`;
2251
2753
  }
2252
2754
  if (tool.configSource === "codacy") return "Codacy";
@@ -2541,18 +3043,16 @@ function updateConfigIncremental(base, current, next) {
2541
3043
  }
2542
3044
 
2543
3045
  // src/init.ts
2544
- var import_promises4 = __toESM(require("fs/promises"));
2545
- var import_os3 = __toESM(require("os"));
2546
- var import_path8 = __toESM(require("path"));
3046
+ var import_path9 = __toESM(require("path"));
2547
3047
  var import_child_process4 = require("child_process");
2548
- var import_tooling7 = require("@codacy/tooling");
3048
+ var import_tooling8 = require("@codacy/tooling");
2549
3049
 
2550
3050
  // src/discover/index.ts
2551
- var import_promises2 = __toESM(require("fs/promises"));
2552
- var import_path6 = __toESM(require("path"));
3051
+ var import_promises3 = __toESM(require("fs/promises"));
3052
+ var import_path7 = __toESM(require("path"));
2553
3053
 
2554
3054
  // src/discover/classify.ts
2555
- var import_tooling6 = require("@codacy/tooling");
3055
+ var import_tooling7 = require("@codacy/tooling");
2556
3056
 
2557
3057
  // src/discover/ecosystems/npm.ts
2558
3058
  function parsePackageJson(content) {
@@ -3687,7 +4187,7 @@ function resolveManifest(filePath) {
3687
4187
  }
3688
4188
 
3689
4189
  // src/discover/signals.ts
3690
- var import_tooling5 = require("@codacy/tooling");
4190
+ var import_tooling6 = require("@codacy/tooling");
3691
4191
  function pathBasename(filePath) {
3692
4192
  return filePath.split("/").pop() ?? filePath;
3693
4193
  }
@@ -3888,7 +4388,7 @@ function detectFileSignals(files) {
3888
4388
  for (const rule of FILE_SIGNAL_RULES) {
3889
4389
  const hit = files.find((f) => rule.matches(f));
3890
4390
  if (!hit) continue;
3891
- const entity = import_tooling5.DEPENDENCY_ENTITIES[rule.entity];
4391
+ const entity = import_tooling6.DEPENDENCY_ENTITIES[rule.entity];
3892
4392
  result.push({
3893
4393
  name: rule.name,
3894
4394
  key: entity.key,
@@ -3912,7 +4412,7 @@ function detectPackageJsonSignals(content) {
3912
4412
  }
3913
4413
  const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
3914
4414
  if (name && isEslintPluginName(name)) {
3915
- const entity = import_tooling5.DEPENDENCY_ENTITIES["eslint-plugin"];
4415
+ const entity = import_tooling6.DEPENDENCY_ENTITIES["eslint-plugin"];
3916
4416
  return [
3917
4417
  {
3918
4418
  name,
@@ -3940,14 +4440,14 @@ function telltaleIndex(ecosystem) {
3940
4440
  }
3941
4441
  function classifyDependency(name, ecosystem) {
3942
4442
  const exact = telltaleIndex(ecosystem).get(name);
3943
- if (exact) return import_tooling6.DEPENDENCY_ENTITIES[exact];
4443
+ if (exact) return import_tooling7.DEPENDENCY_ENTITIES[exact];
3944
4444
  for (const [prefix, entity] of getEcosystem(ecosystem).prefixes ?? []) {
3945
- if (name.startsWith(prefix)) return import_tooling6.DEPENDENCY_ENTITIES[entity];
4445
+ if (name.startsWith(prefix)) return import_tooling7.DEPENDENCY_ENTITIES[entity];
3946
4446
  }
3947
4447
  return void 0;
3948
4448
  }
3949
4449
  function getKnownDisplayNames() {
3950
- return new Set(Object.values(import_tooling6.DEPENDENCY_ENTITIES).map((entity) => entity.displayName));
4450
+ return new Set(Object.values(import_tooling7.DEPENDENCY_ENTITIES).map((entity) => entity.displayName));
3951
4451
  }
3952
4452
 
3953
4453
  // src/discover/notable-files.ts
@@ -4049,7 +4549,12 @@ function isNotableFile(filePath) {
4049
4549
 
4050
4550
  // src/discover/index.ts
4051
4551
  async function discover(repositoryRoot, options) {
4052
- const unfiltered = listRepoFiles(repositoryRoot);
4552
+ let unfiltered;
4553
+ try {
4554
+ unfiltered = listRepoFiles(repositoryRoot);
4555
+ } catch {
4556
+ unfiltered = [];
4557
+ }
4053
4558
  const files = applyExclusions(unfiltered, [...options?.exclude ?? []], []);
4054
4559
  const excludedFiles = unfiltered.length - files.length;
4055
4560
  const filePatterns = extractFilePatterns(files);
@@ -4072,7 +4577,7 @@ async function discover(repositoryRoot, options) {
4072
4577
  frameworks,
4073
4578
  libraries,
4074
4579
  metadata: {
4075
- repositoryRoot: import_path6.default.resolve(repositoryRoot),
4580
+ repositoryRoot: import_path7.default.resolve(repositoryRoot),
4076
4581
  totalFiles: files.length,
4077
4582
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
4078
4583
  // Surfaced so a scan narrowed by excludes is never mistaken for a repo
@@ -4084,7 +4589,7 @@ async function discover(repositoryRoot, options) {
4084
4589
  function extractFilePatterns(files) {
4085
4590
  const extensions = /* @__PURE__ */ new Set();
4086
4591
  for (const file of files) {
4087
- const ext = import_path6.default.extname(file).toLowerCase();
4592
+ const ext = import_path7.default.extname(file).toLowerCase();
4088
4593
  if (ext) {
4089
4594
  extensions.add(`*${ext}`);
4090
4595
  }
@@ -4107,7 +4612,7 @@ async function extractDependencies(repositoryRoot, files) {
4107
4612
  if (!manifest) continue;
4108
4613
  let content;
4109
4614
  try {
4110
- content = await import_promises2.default.readFile(import_path6.default.join(repositoryRoot, filePath), "utf-8");
4615
+ content = await import_promises3.default.readFile(import_path7.default.join(repositoryRoot, filePath), "utf-8");
4111
4616
  } catch {
4112
4617
  continue;
4113
4618
  }
@@ -4147,15 +4652,15 @@ function deduplicateDeps(deps) {
4147
4652
  }
4148
4653
 
4149
4654
  // src/codacy-yaml.ts
4150
- var import_promises3 = __toESM(require("fs/promises"));
4151
- var import_path7 = __toESM(require("path"));
4655
+ var import_promises4 = __toESM(require("fs/promises"));
4656
+ var import_path8 = __toESM(require("path"));
4152
4657
  var import_yaml = require("yaml");
4153
4658
  async function readCodacyYaml(repositoryRoot) {
4154
4659
  const candidates = [".codacy.yaml", ".codacy.yml"];
4155
4660
  let raw = null;
4156
4661
  for (const filename of candidates) {
4157
4662
  try {
4158
- raw = await import_promises3.default.readFile(import_path7.default.join(repositoryRoot, filename), "utf-8");
4663
+ raw = await import_promises4.default.readFile(import_path8.default.join(repositoryRoot, filename), "utf-8");
4159
4664
  break;
4160
4665
  } catch {
4161
4666
  }
@@ -4256,7 +4761,7 @@ async function resolveDiscoveryExcludes(repositoryRoot, adapters, existingConfig
4256
4761
 
4257
4762
  // src/api/client/core/OpenAPI.ts
4258
4763
  var OpenAPI = {
4259
- BASE: "https://app.codacy.com/api/v3",
4764
+ BASE: "https://api.codacy.com/api/v3",
4260
4765
  VERSION: "3.1.0",
4261
4766
  WITH_CREDENTIALS: false,
4262
4767
  CREDENTIALS: "include",
@@ -4435,13 +4940,13 @@ var getQueryString = (params) => {
4435
4940
  };
4436
4941
  var getUrl = (config, options) => {
4437
4942
  const encoder = config.ENCODE_PATH || encodeURI;
4438
- const path10 = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => {
4943
+ const path12 = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => {
4439
4944
  if (options.path?.hasOwnProperty(group)) {
4440
4945
  return encoder(String(options.path[group]));
4441
4946
  }
4442
4947
  return substring;
4443
4948
  });
4444
- const url = `${config.BASE}${path10}`;
4949
+ const url = `${config.BASE}${path12}`;
4445
4950
  if (options.query) {
4446
4951
  return `${url}${getQueryString(options.query)}`;
4447
4952
  }
@@ -5176,10 +5681,10 @@ var AnalysisService = class {
5176
5681
  * Patterns configuration for (repository, tool). Uses standard if applied, repository settings otherwise.
5177
5682
  *
5178
5683
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param toolUuid Unique identifier (UUID) for the tool* @param languages Comma-separated list of programming languages to filter results by* @param categories Filter by a comma-separated list of code pattern categories. Valid values are `Security`, `ErrorProne`, `CodeStyle`, `Compatibility`, `UnusedCode`, `Complexity`, `Comprehensibility`, `Documentation`, `BestPractice`, and `Performance`.
5179
- * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param enabled Filter by pattern status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns* @param sort Field used to sort the tool's code patterns. Valid values are `category`, `recommended`, and `severity`.* @param direction Sort direction. Possible values are 'asc' (ascending) or 'desc' (descending).* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ConfiguredPatternsListResponse Successful operation
5684
+ * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param enabled Filter by pattern status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns* @param matchesStack Filter repository code patterns by whether they match the repository stack.* @param sort Field used to sort the tool's code patterns. Valid values are `category`, `recommended`, and `severity`.* @param direction Sort direction. Possible values are 'asc' (ascending) or 'desc' (descending).* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ConfiguredPatternsListResponse Successful operation
5180
5685
  * @throws ApiError
5181
5686
  */
5182
- static listRepositoryToolPatterns(provider, remoteOrganizationName, repositoryName, toolUuid, languages, categories, severityLevels, tags, search, enabled, recommended, sort, direction, cursor, limit = 100, abortSignal) {
5687
+ static listRepositoryToolPatterns(provider, remoteOrganizationName, repositoryName, toolUuid, languages, categories, severityLevels, tags, search, enabled, recommended, matchesStack, sort, direction, cursor, limit = 100, abortSignal) {
5183
5688
  return request(OpenAPI, {
5184
5689
  method: "GET",
5185
5690
  url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/tools/{toolUuid}/patterns",
@@ -5197,6 +5702,7 @@ var AnalysisService = class {
5197
5702
  "search": search,
5198
5703
  "enabled": enabled,
5199
5704
  "recommended": recommended,
5705
+ "matchesStack": matchesStack,
5200
5706
  "sort": sort,
5201
5707
  "direction": direction,
5202
5708
  "cursor": cursor,
@@ -5218,10 +5724,10 @@ var AnalysisService = class {
5218
5724
  * Use filters to specify the code patterns to update, or omit the filters to update all code patterns.
5219
5725
  *
5220
5726
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param toolUuid Unique identifier (UUID) for the tool* @param requestBody * @param languages Comma-separated list of programming languages to filter results by* @param categories Filter by a comma-separated list of code pattern categories. Valid values are `Security`, `ErrorProne`, `CodeStyle`, `Compatibility`, `UnusedCode`, `Complexity`, `Comprehensibility`, `Documentation`, `BestPractice`, and `Performance`.
5221
- * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns * @returns void
5727
+ * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns* @param matchesStack Filter repository code patterns by whether they match the repository stack. * @returns void
5222
5728
  * @throws ApiError
5223
5729
  */
5224
- static updateRepositoryToolPatterns(provider, remoteOrganizationName, repositoryName, toolUuid, requestBody, languages, categories, severityLevels, tags, search, recommended, abortSignal) {
5730
+ static updateRepositoryToolPatterns(provider, remoteOrganizationName, repositoryName, toolUuid, requestBody, languages, categories, severityLevels, tags, search, recommended, matchesStack, abortSignal) {
5225
5731
  return request(OpenAPI, {
5226
5732
  method: "PATCH",
5227
5733
  url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/tools/{toolUuid}/patterns",
@@ -5237,7 +5743,8 @@ var AnalysisService = class {
5237
5743
  "severityLevels": severityLevels,
5238
5744
  "tags": tags,
5239
5745
  "search": search,
5240
- "recommended": recommended
5746
+ "recommended": recommended,
5747
+ "matchesStack": matchesStack
5241
5748
  },
5242
5749
  abortSignal,
5243
5750
  body: requestBody,
@@ -5285,10 +5792,10 @@ var AnalysisService = class {
5285
5792
  * Patterns overview for tool. Uses standard if applied, repository settings otherwise.
5286
5793
  *
5287
5794
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param toolUuid Unique identifier (UUID) for the tool* @param languages Comma-separated list of programming languages to filter results by* @param categories Filter by a comma-separated list of code pattern categories. Valid values are `Security`, `ErrorProne`, `CodeStyle`, `Compatibility`, `UnusedCode`, `Complexity`, `Comprehensibility`, `Documentation`, `BestPractice`, and `Performance`.
5288
- * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param enabled Filter by pattern status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns * @returns ToolPatternsOverviewResponse Successful operation
5795
+ * * @param severityLevels Filter by a comma-separated list of code pattern severity levels. Valid values are `Error`, `High`, `Warning`, and `Info`.* @param tags Filter by a comma-separated list of pattern tags* @param search Filter results by searching for this string* @param enabled Filter by pattern status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns* @param recommended Filter by recommended status. Set to `true` to return only recommended patterns, or `false` to return only non-recommended patterns* @param matchesStack Filter repository code patterns by whether they match the repository stack. * @returns ToolPatternsOverviewResponse Successful operation
5289
5796
  * @throws ApiError
5290
5797
  */
5291
- static toolPatternsOverview(provider, remoteOrganizationName, repositoryName, toolUuid, languages, categories, severityLevels, tags, search, enabled, recommended, abortSignal) {
5798
+ static toolPatternsOverview(provider, remoteOrganizationName, repositoryName, toolUuid, languages, categories, severityLevels, tags, search, enabled, recommended, matchesStack, abortSignal) {
5292
5799
  return request(OpenAPI, {
5293
5800
  method: "GET",
5294
5801
  url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/tools/{toolUuid}/patterns/overview",
@@ -5305,7 +5812,8 @@ var AnalysisService = class {
5305
5812
  "tags": tags,
5306
5813
  "search": search,
5307
5814
  "enabled": enabled,
5308
- "recommended": recommended
5815
+ "recommended": recommended,
5816
+ "matchesStack": matchesStack
5309
5817
  },
5310
5818
  abortSignal,
5311
5819
  errors: {
@@ -5377,16 +5885,134 @@ var AnalysisService = class {
5377
5885
  }
5378
5886
  });
5379
5887
  }
5888
+ /**
5889
+ * Recover a stuck repository
5890
+ * Triggers a recovery action when the repository is stuck on its first analysis.
5891
+ * The exact action taken depends on the stuck state.
5892
+ * Returns `422 Unprocessable Entity` when the repository is not stuck.
5893
+ *
5894
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param branch Name of a [repository branch enabled on Codacy](https://docs.codacy.com/repositories-configure/managing-branches/),
5895
+ * as returned by the endpoint [listRepositoryBranches](#listrepositorybranches).
5896
+ * By default, uses the main branch defined on the Codacy repository settings.
5897
+ * * @returns void
5898
+ * @throws ApiError
5899
+ */
5900
+ static recoverRepository(provider, remoteOrganizationName, repositoryName, branch, abortSignal) {
5901
+ return request(OpenAPI, {
5902
+ method: "POST",
5903
+ url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/recover",
5904
+ path: {
5905
+ "provider": provider,
5906
+ "remoteOrganizationName": remoteOrganizationName,
5907
+ "repositoryName": repositoryName
5908
+ },
5909
+ query: {
5910
+ "branch": branch
5911
+ },
5912
+ abortSignal,
5913
+ errors: {
5914
+ 400: `Bad Request`,
5915
+ 401: `Unauthorized`,
5916
+ 403: `Forbidden`,
5917
+ 404: `Not Found`,
5918
+ 422: `Unprocessable Entity`,
5919
+ 500: `Internal Server Error`
5920
+ }
5921
+ });
5922
+ }
5923
+ /**
5924
+ * Add repository autoconfiguration run
5925
+ * **[Experimental]** Autoconfig enqueuing endpoint
5926
+ *
5927
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns AddAutoconfigResponse Autoconfig run accepted
5928
+ * @throws ApiError
5929
+ */
5930
+ static addAutoconfig(provider, remoteOrganizationName, repositoryName, abortSignal) {
5931
+ return request(OpenAPI, {
5932
+ method: "POST",
5933
+ url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/autoconfig",
5934
+ path: {
5935
+ "provider": provider,
5936
+ "remoteOrganizationName": remoteOrganizationName,
5937
+ "repositoryName": repositoryName
5938
+ },
5939
+ abortSignal,
5940
+ errors: {
5941
+ 400: `Bad Request`,
5942
+ 401: `Unauthorized`,
5943
+ 403: `Forbidden`,
5944
+ 404: `Not Found`,
5945
+ 422: `Unprocessable Entity`,
5946
+ 429: `Too Many Requests`,
5947
+ 500: `Internal Server Error`
5948
+ }
5949
+ });
5950
+ }
5951
+ /**
5952
+ * Get latest autoconfig run summary
5953
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns AutoconfigRunSummaryResponse Successful operation
5954
+ * @throws ApiError
5955
+ */
5956
+ static getLatestAutoconfigRunSummary(provider, remoteOrganizationName, repositoryName, abortSignal) {
5957
+ return request(OpenAPI, {
5958
+ method: "GET",
5959
+ url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/autoconfig/runs",
5960
+ path: {
5961
+ "provider": provider,
5962
+ "remoteOrganizationName": remoteOrganizationName,
5963
+ "repositoryName": repositoryName
5964
+ },
5965
+ abortSignal,
5966
+ errors: {
5967
+ 400: `Bad Request`,
5968
+ 401: `Unauthorized`,
5969
+ 403: `Forbidden`,
5970
+ 404: `Not Found`,
5971
+ 500: `Internal Server Error`
5972
+ }
5973
+ });
5974
+ }
5975
+ /**
5976
+ * Get autoconfig run status
5977
+ * **[Experimental]** Fetch latest autoconfig run state managed (can be `Requested`, `Running`, `Done` or `Failed`)
5978
+ *
5979
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns AutoconfigStatusResponse Successful operation
5980
+ * @throws ApiError
5981
+ */
5982
+ static getAutoconfigStatus(provider, remoteOrganizationName, repositoryName, abortSignal) {
5983
+ return request(OpenAPI, {
5984
+ method: "GET",
5985
+ url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/autoconfig/status",
5986
+ path: {
5987
+ "provider": provider,
5988
+ "remoteOrganizationName": remoteOrganizationName,
5989
+ "repositoryName": repositoryName
5990
+ },
5991
+ abortSignal,
5992
+ errors: {
5993
+ 400: `Bad Request`,
5994
+ 401: `Unauthorized`,
5995
+ 403: `Forbidden`,
5996
+ 404: `Not Found`,
5997
+ 500: `Internal Server Error`
5998
+ }
5999
+ });
6000
+ }
5380
6001
  /**
5381
6002
  * List pull requests from a repository that the user has access to
5382
- * You can search this endpoint for either `last-updated` (default), `impact` or `merged`
6003
+ * You can search this endpoint for either `last-updated` (default), `merged`
5383
6004
  *
5384
6005
  * **Note:** When applied to public repositories, this operation does not require authentication.
5385
6006
  *
5386
- * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param limit Maximum number of items to return* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param search Filter results by searching for this string* @param includeNotAnalyzed If true, also return pull requests that weren't analyzed * @returns PullRequestWithAnalysisListResponse Successful operation
6007
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param limit Maximum number of items to return* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param search Filter results by searching for this string* @param textQuery Filter pull requests by a free-text search matched, case-insensitively, against the
6008
+ * pull request title or the author's handle.
6009
+ * * @param targetBranch Filter pull requests by the name of their target branch.
6010
+ * Pull requests targeting any other branch are excluded from the results.
6011
+ * By default, pull requests targeting any branch are returned.
6012
+ * * @param includeNotAnalyzed If true, also return pull requests that weren't analyzed * @returns PullRequestWithAnalysisListResponse Successful operation
5387
6013
  * @throws ApiError
5388
6014
  */
5389
- static listRepositoryPullRequests(provider, remoteOrganizationName, repositoryName, limit = 100, cursor, search, includeNotAnalyzed = false, abortSignal) {
6015
+ static listRepositoryPullRequests(provider, remoteOrganizationName, repositoryName, limit = 100, cursor, search, textQuery, targetBranch, includeNotAnalyzed = false, abortSignal) {
5390
6016
  return request(OpenAPI, {
5391
6017
  method: "GET",
5392
6018
  url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/pull-requests",
@@ -5399,6 +6025,8 @@ var AnalysisService = class {
5399
6025
  "limit": limit,
5400
6026
  "cursor": cursor,
5401
6027
  "search": search,
6028
+ "textQuery": textQuery,
6029
+ "targetBranch": targetBranch,
5402
6030
  "includeNotAnalyzed": includeNotAnalyzed
5403
6031
  },
5404
6032
  abortSignal,
@@ -5494,6 +6122,33 @@ var AnalysisService = class {
5494
6122
  }
5495
6123
  });
5496
6124
  }
6125
+ /**
6126
+ * Triggers an AI review for a pull request
6127
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param pullRequestNumber Pull request number * @returns void
6128
+ * @throws ApiError
6129
+ */
6130
+ static triggerPullRequestAiReview(provider, remoteOrganizationName, repositoryName, pullRequestNumber, abortSignal) {
6131
+ return request(OpenAPI, {
6132
+ method: "POST",
6133
+ url: "/analysis/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/pull-requests/{pullRequestNumber}/ai-reviewer/trigger",
6134
+ path: {
6135
+ "provider": provider,
6136
+ "remoteOrganizationName": remoteOrganizationName,
6137
+ "repositoryName": repositoryName,
6138
+ "pullRequestNumber": pullRequestNumber
6139
+ },
6140
+ abortSignal,
6141
+ errors: {
6142
+ 400: `Bad Request`,
6143
+ 401: `Unauthorized`,
6144
+ 403: `Forbidden`,
6145
+ 404: `Not Found`,
6146
+ 409: `Conflict`,
6147
+ 422: `Unprocessable Entity`,
6148
+ 500: `Internal Server Error`
6149
+ }
6150
+ });
6151
+ }
5497
6152
  /**
5498
6153
  * List all coverage reports uploaded for the common ancestor commit and head commit of a pull request branch
5499
6154
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param pullRequestNumber Pull request number * @returns CoveragePullRequestResponse Successful operation
@@ -5767,7 +6422,7 @@ var AnalysisService = class {
5767
6422
  }
5768
6423
  /**
5769
6424
  * List organization pull requests from repositories that the user has access to
5770
- * You can sort by `last-updated` (default), `impact`, or `merged`
6425
+ * You can sort by `last-updated` (default), `merged`
5771
6426
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param limit Maximum number of items to return* @param search Filter results by searching for this string* @param repositories **Deprecated:** Use [searchOrganizationRepositoriesWithAnalysis](#searchorganizationrepositorieswithanalysis) instead. * @returns PullRequestWithAnalysisListResponse Successful operation
5772
6427
  * @throws ApiError
5773
6428
  */
@@ -5896,7 +6551,7 @@ var AnalysisService = class {
5896
6551
  }
5897
6552
  /**
5898
6553
  * Bulk ignore issues in a repository
5899
- * Receives a list of issue ids and ignores them in the specified repository. Limited to 50 issues at once
6554
+ * Receives a list of issue ids and ignores them in the specified repository. Limited to 100 issues at once
5900
6555
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param requestBody List of issue ids to ignore * @returns void
5901
6556
  * @throws ApiError
5902
6557
  */
@@ -7114,10 +7769,16 @@ var RepositoryService = class {
7114
7769
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param branch Name of a [repository branch enabled on Codacy](https://docs.codacy.com/repositories-configure/managing-branches/),
7115
7770
  * as returned by the endpoint [listRepositoryBranches](#listrepositorybranches).
7116
7771
  * By default, uses the main branch defined on the Codacy repository settings.
7117
- * * @param search Filter files that include this string anywhere in their relative path* @param sort Field used to sort the list of files. Valid values are `filename`, `issues`, `grade`, `duplication`, `complexity`, and `coverage`.* @param direction Sort direction. Possible values are 'asc' (ascending) or 'desc' (descending).* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns FileListResponse List of files in the repository
7772
+ * * @param path Relative path of the folder whose direct child files are listed.
7773
+ * When absent, all files in the repository are returned (recursive).
7774
+ * When empty, only files at the repository root are returned.
7775
+ * * @param search Filter files that include this string anywhere in their relative path* @param sort Field used to sort the results.
7776
+ * For files the valid values are `filename`, `issues`, `grade`, `duplication`, `complexity`, and `coverage`.
7777
+ * For folders the valid values are `name`, `issues`, `grade`, `duplication`, `complexity`, and `coverage`.
7778
+ * * @param direction Sort direction. Possible values are 'asc' (ascending) or 'desc' (descending).* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns FileListResponse List of files in the repository
7118
7779
  * @throws ApiError
7119
7780
  */
7120
- static listFiles(provider, remoteOrganizationName, repositoryName, branch, search, sort, direction, cursor, limit = 100, abortSignal) {
7781
+ static listFiles(provider, remoteOrganizationName, repositoryName, branch, path12, search, sort, direction, cursor, limit = 100, abortSignal) {
7121
7782
  return request(OpenAPI, {
7122
7783
  method: "GET",
7123
7784
  url: "/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/files",
@@ -7128,6 +7789,7 @@ var RepositoryService = class {
7128
7789
  },
7129
7790
  query: {
7130
7791
  "branch": branch,
7792
+ "path": path12,
7131
7793
  "search": search,
7132
7794
  "sort": sort,
7133
7795
  "direction": direction,
@@ -7144,6 +7806,48 @@ var RepositoryService = class {
7144
7806
  }
7145
7807
  });
7146
7808
  }
7809
+ /**
7810
+ * List folders in a repository
7811
+ * Returns the most recent analysis information for the folders directly inside a path of a repository, with metrics aggregated over every file in each folder's subtree.
7812
+ * Use the `path` parameter to list the folders inside a folder; when absent or empty, the folders at the repository root are returned.
7813
+ *
7814
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param branch Name of a [repository branch enabled on Codacy](https://docs.codacy.com/repositories-configure/managing-branches/),
7815
+ * as returned by the endpoint [listRepositoryBranches](#listrepositorybranches).
7816
+ * By default, uses the main branch defined on the Codacy repository settings.
7817
+ * * @param path Path of the folder whose child folders are listed.
7818
+ * When absent or empty, the folders at the repository root are returned.
7819
+ * * @param sort Field used to sort the results.
7820
+ * For files the valid values are `filename`, `issues`, `grade`, `duplication`, `complexity`, and `coverage`.
7821
+ * For folders the valid values are `name`, `issues`, `grade`, `duplication`, `complexity`, and `coverage`.
7822
+ * * @param direction Sort direction. Possible values are 'asc' (ascending) or 'desc' (descending).* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns DirectoryListResponse List of folders in the repository
7823
+ * @throws ApiError
7824
+ */
7825
+ static listDirectories(provider, remoteOrganizationName, repositoryName, branch, path12, sort, direction, cursor, limit = 100, abortSignal) {
7826
+ return request(OpenAPI, {
7827
+ method: "GET",
7828
+ url: "/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/directories",
7829
+ path: {
7830
+ "provider": provider,
7831
+ "remoteOrganizationName": remoteOrganizationName,
7832
+ "repositoryName": repositoryName
7833
+ },
7834
+ query: {
7835
+ "branch": branch,
7836
+ "path": path12,
7837
+ "sort": sort,
7838
+ "direction": direction,
7839
+ "cursor": cursor,
7840
+ "limit": limit
7841
+ },
7842
+ abortSignal,
7843
+ errors: {
7844
+ 400: `Bad Request`,
7845
+ 401: `Unauthorized`,
7846
+ 404: `Not Found`,
7847
+ 500: `Internal Server Error`
7848
+ }
7849
+ });
7850
+ }
7147
7851
  /**
7148
7852
  * List ignored files in a repository
7149
7853
  * Returns the most recent ignored files information.
@@ -7267,6 +7971,9 @@ var RepositoryService = class {
7267
7971
  }
7268
7972
  /**
7269
7973
  * List the [repository API tokens](https://docs.codacy.com/codacy-api/api-tokens/)
7974
+ * Each token reports its name, expiration and creation date. Tokens created before creation dates were
7975
+ * recorded have no `createdAt`
7976
+ *
7270
7977
  * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns ApiTokenListResponse Successful operation
7271
7978
  * @throws ApiError
7272
7979
  */
@@ -7291,10 +7998,10 @@ var RepositoryService = class {
7291
7998
  }
7292
7999
  /**
7293
8000
  * Create a new [repository API token](https://docs.codacy.com/codacy-api/api-tokens/)
7294
- * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns ApiTokenResponse Successful operation
8001
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param requestBody * @returns ApiTokenResponse Successful operation
7295
8002
  * @throws ApiError
7296
8003
  */
7297
- static createRepositoryApiToken(provider, remoteOrganizationName, repositoryName, abortSignal) {
8004
+ static createRepositoryApiToken(provider, remoteOrganizationName, repositoryName, requestBody, abortSignal) {
7298
8005
  return request(OpenAPI, {
7299
8006
  method: "POST",
7300
8007
  url: "/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/tokens",
@@ -7304,6 +8011,35 @@ var RepositoryService = class {
7304
8011
  "repositoryName": repositoryName
7305
8012
  },
7306
8013
  abortSignal,
8014
+ body: requestBody,
8015
+ mediaType: "application/json",
8016
+ errors: {
8017
+ 400: `Bad Request`,
8018
+ 401: `Unauthorized`,
8019
+ 404: `Not Found`,
8020
+ 422: `Unprocessable Entity`,
8021
+ 500: `Internal Server Error`
8022
+ }
8023
+ });
8024
+ }
8025
+ /**
8026
+ * Delete multiple [repository API tokens](https://docs.codacy.com/codacy-api/api-tokens/) by id
8027
+ * Deletes the repository API tokens with the given ids
8028
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization* @param requestBody * @returns void
8029
+ * @throws ApiError
8030
+ */
8031
+ static deleteRepositoryApiTokens(provider, remoteOrganizationName, repositoryName, requestBody, abortSignal) {
8032
+ return request(OpenAPI, {
8033
+ method: "POST",
8034
+ url: "/organizations/{provider}/{remoteOrganizationName}/repositories/{repositoryName}/tokens/delete",
8035
+ path: {
8036
+ "provider": provider,
8037
+ "remoteOrganizationName": remoteOrganizationName,
8038
+ "repositoryName": repositoryName
8039
+ },
8040
+ abortSignal,
8041
+ body: requestBody,
8042
+ mediaType: "application/json",
7307
8043
  errors: {
7308
8044
  400: `Bad Request`,
7309
8045
  401: `Unauthorized`,
@@ -7555,97 +8291,347 @@ var RepositoryService = class {
7555
8291
  }
7556
8292
  };
7557
8293
 
7558
- // src/api/client/services/ToolsService.ts
7559
- var ToolsService = class {
8294
+ // src/api/client/services/SbomService.ts
8295
+ var SbomService = class {
7560
8296
  /**
7561
- * Retrieve the list of tools
7562
- * @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ToolListResponse Successful operation
8297
+ * Return a list of SBOM dependencies, used across the organization.
8298
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return* @param sortColumn Field used to sort the results. The possible values are `severity` (default, to sort by the item's severity), or `order` (to sort by the OSSF order).* @param columnOrder Sort direction. The possible values are `asc` (ascending) or `desc` (descending - default).* @param requestBody Request body with filters. * @returns SearchSbomDependenciesResponse Successful operation
7563
8299
  * @throws ApiError
7564
8300
  */
7565
- static listTools(cursor, limit = 100, abortSignal) {
8301
+ static searchSbomDependencies(provider, remoteOrganizationName, cursor, limit = 100, sortColumn = "severity", columnOrder = "desc", requestBody, abortSignal) {
7566
8302
  return request(OpenAPI, {
7567
- method: "GET",
7568
- url: "/tools",
8303
+ method: "POST",
8304
+ url: "/organizations/{provider}/{remoteOrganizationName}/sbom/dependencies/search",
8305
+ path: {
8306
+ "provider": provider,
8307
+ "remoteOrganizationName": remoteOrganizationName
8308
+ },
7569
8309
  query: {
7570
8310
  "cursor": cursor,
7571
- "limit": limit
8311
+ "limit": limit,
8312
+ "sortColumn": sortColumn,
8313
+ "columnOrder": columnOrder
7572
8314
  },
7573
8315
  abortSignal,
8316
+ body: requestBody,
8317
+ mediaType: "application/json",
7574
8318
  errors: {
7575
8319
  400: `Bad Request`,
8320
+ 401: `Unauthorized`,
8321
+ 403: `Forbidden`,
8322
+ 404: `Not Found`,
7576
8323
  422: `Unprocessable Entity`,
7577
- 500: `Internal Server Error`
8324
+ 500: `Internal Server Error`,
8325
+ 501: `Not Implemented`,
8326
+ 502: `Bad Gateway`
7578
8327
  }
7579
8328
  });
7580
8329
  }
7581
8330
  /**
7582
- * Retrieve the list of tool patterns
7583
- * @param toolUuid Unique identifier (UUID) for the tool* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return* @param enabled Filter by enabled status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns. * @returns PatternListResponse Successful operations
8331
+ * Return a paginated list of repositories where a version of the specified SBOM dependency is used.
8332
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param requestBody Request body with filters.* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns SearchRepositoriesOfSbomDependencyResponse Successful operation
7584
8333
  * @throws ApiError
7585
8334
  */
7586
- static listPatterns(toolUuid, cursor, limit = 100, enabled, abortSignal) {
8335
+ static searchRepositoriesOfSbomDependency(provider, remoteOrganizationName, requestBody, cursor, limit = 100, abortSignal) {
7587
8336
  return request(OpenAPI, {
7588
- method: "GET",
7589
- url: "/tools/{toolUuid}/patterns",
8337
+ method: "POST",
8338
+ url: "/organizations/{provider}/{remoteOrganizationName}/sbom/dependencies/repositories/search",
7590
8339
  path: {
7591
- "toolUuid": toolUuid
8340
+ "provider": provider,
8341
+ "remoteOrganizationName": remoteOrganizationName
7592
8342
  },
7593
8343
  query: {
7594
8344
  "cursor": cursor,
7595
- "limit": limit,
7596
- "enabled": enabled
8345
+ "limit": limit
7597
8346
  },
7598
8347
  abortSignal,
8348
+ body: requestBody,
8349
+ mediaType: "application/json",
7599
8350
  errors: {
7600
8351
  400: `Bad Request`,
8352
+ 401: `Unauthorized`,
8353
+ 403: `Forbidden`,
7601
8354
  404: `Not Found`,
7602
8355
  422: `Unprocessable Entity`,
7603
- 500: `Internal Server Error`
8356
+ 500: `Internal Server Error`,
8357
+ 501: `Not Implemented`,
8358
+ 502: `Bad Gateway`
7604
8359
  }
7605
8360
  });
7606
8361
  }
7607
8362
  /**
7608
- * Add feedback relating to the tool pattern
7609
- * Add feedback relating to the tool pattern.
7610
- *
7611
- * @param toolUuid Unique identifier (UUID) for the tool* @param patternId Pattern identifier* @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param requestBody * @returns any Successful operation
8363
+ * List repositories with SBOM dependency information
8364
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return* @param requestBody Request body with filters. * @returns SearchSbomRepositoriesResponse Successful operation
7612
8365
  * @throws ApiError
7613
8366
  */
7614
- static addPatternFeedback(toolUuid, patternId, provider, remoteOrganizationName, requestBody, abortSignal) {
8367
+ static searchSbomRepositories(provider, remoteOrganizationName, cursor, limit = 100, requestBody, abortSignal) {
7615
8368
  return request(OpenAPI, {
7616
8369
  method: "POST",
7617
- url: "/tools/{toolUuid}/patterns/{patternId}/organizations/{provider}/{remoteOrganizationName}/feedback",
8370
+ url: "/organizations/{provider}/{remoteOrganizationName}/sbom/repositories/search",
7618
8371
  path: {
7619
- "toolUuid": toolUuid,
7620
- "patternId": patternId,
7621
8372
  "provider": provider,
7622
8373
  "remoteOrganizationName": remoteOrganizationName
7623
8374
  },
8375
+ query: {
8376
+ "cursor": cursor,
8377
+ "limit": limit
8378
+ },
7624
8379
  abortSignal,
7625
8380
  body: requestBody,
7626
8381
  mediaType: "application/json",
7627
8382
  errors: {
7628
8383
  400: `Bad Request`,
7629
8384
  401: `Unauthorized`,
8385
+ 403: `Forbidden`,
7630
8386
  404: `Not Found`,
7631
8387
  422: `Unprocessable Entity`,
7632
- 500: `Internal Server Error`
8388
+ 500: `Internal Server Error`,
8389
+ 501: `Not Implemented`,
8390
+ 502: `Bad Gateway`
7633
8391
  }
7634
8392
  });
7635
8393
  }
7636
8394
  /**
7637
- * Retrieve a tool pattern
7638
- * Get the definition of a specific pattern
7639
- * @param toolUuid Unique identifier (UUID) for the tool* @param patternId Pattern identifier * @returns PatternResponse Successful operation
8395
+ * Get a presigned URL for the latest SBOM of the repository
8396
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param repositoryName Repository name on the Git provider organization * @returns SbomPresignResponse Successful operation.
7640
8397
  * @throws ApiError
7641
8398
  */
7642
- static getPattern(toolUuid, patternId, abortSignal) {
8399
+ static getRepositorySbomPresignedUrl(provider, remoteOrganizationName, repositoryName, abortSignal) {
7643
8400
  return request(OpenAPI, {
7644
8401
  method: "GET",
7645
- url: "/tools/{toolUuid}/patterns/{patternId}",
8402
+ url: "/organizations/{provider}/{remoteOrganizationName}/projects/{repositoryName}/sbom",
7646
8403
  path: {
7647
- "toolUuid": toolUuid,
7648
- "patternId": patternId
8404
+ "provider": provider,
8405
+ "remoteOrganizationName": remoteOrganizationName,
8406
+ "repositoryName": repositoryName
8407
+ },
8408
+ abortSignal,
8409
+ errors: {
8410
+ 400: `Bad Request`,
8411
+ 401: `Unauthorized`,
8412
+ 404: `Not Found`,
8413
+ 500: `Internal Server Error`
8414
+ }
8415
+ });
8416
+ }
8417
+ /**
8418
+ * Upload an SBOM for a Docker image
8419
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param formData * @returns void
8420
+ * @throws ApiError
8421
+ */
8422
+ static uploadImageSbom(provider, remoteOrganizationName, formData, abortSignal) {
8423
+ return request(OpenAPI, {
8424
+ method: "POST",
8425
+ url: "/organizations/{provider}/{remoteOrganizationName}/image-sboms",
8426
+ path: {
8427
+ "provider": provider,
8428
+ "remoteOrganizationName": remoteOrganizationName
8429
+ },
8430
+ abortSignal,
8431
+ formData,
8432
+ mediaType: "multipart/form-data",
8433
+ errors: {
8434
+ 400: `Bad Request`,
8435
+ 401: `Unauthorized`,
8436
+ 404: `Not Found`,
8437
+ 409: `Conflict`,
8438
+ 500: `Internal Server Error`
8439
+ }
8440
+ });
8441
+ }
8442
+ /**
8443
+ * Delete all SBOMs for a given image
8444
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param imageName Image name of the SBOM Image * @returns void
8445
+ * @throws ApiError
8446
+ */
8447
+ static deleteImageSboms(provider, remoteOrganizationName, imageName, abortSignal) {
8448
+ return request(OpenAPI, {
8449
+ method: "DELETE",
8450
+ url: "/organizations/{provider}/{remoteOrganizationName}/image-sboms/{imageName}",
8451
+ path: {
8452
+ "provider": provider,
8453
+ "remoteOrganizationName": remoteOrganizationName,
8454
+ "imageName": imageName
8455
+ },
8456
+ abortSignal,
8457
+ errors: {
8458
+ 400: `Bad Request`,
8459
+ 401: `Unauthorized`,
8460
+ 404: `Not Found`,
8461
+ 500: `Internal Server Error`
8462
+ }
8463
+ });
8464
+ }
8465
+ /**
8466
+ * Delete SBOM for a given image/tag combination
8467
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param imageName Image name of the SBOM Image* @param tag Image Tag of the SBOM Image * @returns void
8468
+ * @throws ApiError
8469
+ */
8470
+ static deleteImageTag(provider, remoteOrganizationName, imageName, tag, abortSignal) {
8471
+ return request(OpenAPI, {
8472
+ method: "DELETE",
8473
+ url: "/organizations/{provider}/{remoteOrganizationName}/image-sboms/{imageName}/tags/{tag}",
8474
+ path: {
8475
+ "provider": provider,
8476
+ "remoteOrganizationName": remoteOrganizationName,
8477
+ "imageName": imageName,
8478
+ "tag": tag
8479
+ },
8480
+ abortSignal,
8481
+ errors: {
8482
+ 400: `Bad Request`,
8483
+ 401: `Unauthorized`,
8484
+ 404: `Not Found`,
8485
+ 500: `Internal Server Error`
8486
+ }
8487
+ });
8488
+ }
8489
+ /**
8490
+ * List Docker images for an organization
8491
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ListImagesResponse Successful operation
8492
+ * @throws ApiError
8493
+ */
8494
+ static listOrganizationImages(provider, remoteOrganizationName, cursor, limit = 100, abortSignal) {
8495
+ return request(OpenAPI, {
8496
+ method: "GET",
8497
+ url: "/organizations/{provider}/{remoteOrganizationName}/images",
8498
+ path: {
8499
+ "provider": provider,
8500
+ "remoteOrganizationName": remoteOrganizationName
8501
+ },
8502
+ query: {
8503
+ "cursor": cursor,
8504
+ "limit": limit
8505
+ },
8506
+ abortSignal,
8507
+ errors: {
8508
+ 400: `Bad Request`,
8509
+ 401: `Unauthorized`,
8510
+ 404: `Not Found`,
8511
+ 500: `Internal Server Error`
8512
+ }
8513
+ });
8514
+ }
8515
+ /**
8516
+ * List Docker image tags for an organization
8517
+ * @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param imageName Image name of the SBOM Image* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ListImageTagsResponse Successful operation
8518
+ * @throws ApiError
8519
+ */
8520
+ static listImageTags(provider, remoteOrganizationName, imageName, cursor, limit = 100, abortSignal) {
8521
+ return request(OpenAPI, {
8522
+ method: "GET",
8523
+ url: "/organizations/{provider}/{remoteOrganizationName}/images/{imageName}/tags",
8524
+ path: {
8525
+ "provider": provider,
8526
+ "remoteOrganizationName": remoteOrganizationName,
8527
+ "imageName": imageName
8528
+ },
8529
+ query: {
8530
+ "cursor": cursor,
8531
+ "limit": limit
8532
+ },
8533
+ abortSignal,
8534
+ errors: {
8535
+ 400: `Bad Request`,
8536
+ 401: `Unauthorized`,
8537
+ 404: `Not Found`,
8538
+ 500: `Internal Server Error`
8539
+ }
8540
+ });
8541
+ }
8542
+ };
8543
+
8544
+ // src/api/client/services/ToolsService.ts
8545
+ var ToolsService = class {
8546
+ /**
8547
+ * Retrieve the list of tools
8548
+ * @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return * @returns ToolListResponse Successful operation
8549
+ * @throws ApiError
8550
+ */
8551
+ static listTools(cursor, limit = 100, abortSignal) {
8552
+ return request(OpenAPI, {
8553
+ method: "GET",
8554
+ url: "/tools",
8555
+ query: {
8556
+ "cursor": cursor,
8557
+ "limit": limit
8558
+ },
8559
+ abortSignal,
8560
+ errors: {
8561
+ 400: `Bad Request`,
8562
+ 422: `Unprocessable Entity`,
8563
+ 500: `Internal Server Error`
8564
+ }
8565
+ });
8566
+ }
8567
+ /**
8568
+ * Retrieve the list of tool patterns
8569
+ * @param toolUuid Unique identifier (UUID) for the tool* @param cursor Cursor to [specify a batch of results to request](https://docs.codacy.com/codacy-api/using-the-codacy-api/#using-pagination)* @param limit Maximum number of items to return* @param enabled Filter by enabled status. Set to `true` to return only enabled patterns, or `false` to return only disabled patterns. * @returns PatternListResponse Successful operations
8570
+ * @throws ApiError
8571
+ */
8572
+ static listPatterns(toolUuid, cursor, limit = 100, enabled, abortSignal) {
8573
+ return request(OpenAPI, {
8574
+ method: "GET",
8575
+ url: "/tools/{toolUuid}/patterns",
8576
+ path: {
8577
+ "toolUuid": toolUuid
8578
+ },
8579
+ query: {
8580
+ "cursor": cursor,
8581
+ "limit": limit,
8582
+ "enabled": enabled
8583
+ },
8584
+ abortSignal,
8585
+ errors: {
8586
+ 400: `Bad Request`,
8587
+ 404: `Not Found`,
8588
+ 422: `Unprocessable Entity`,
8589
+ 500: `Internal Server Error`
8590
+ }
8591
+ });
8592
+ }
8593
+ /**
8594
+ * Add feedback relating to the tool pattern
8595
+ * Add feedback relating to the tool pattern.
8596
+ *
8597
+ * @param toolUuid Unique identifier (UUID) for the tool* @param patternId Pattern identifier* @param provider Git provider identifier (e.g., gh for GitHub, gl for GitLab, bb for Bitbucket)* @param remoteOrganizationName Organization name on the Git provider* @param requestBody * @returns any Successful operation
8598
+ * @throws ApiError
8599
+ */
8600
+ static addPatternFeedback(toolUuid, patternId, provider, remoteOrganizationName, requestBody, abortSignal) {
8601
+ return request(OpenAPI, {
8602
+ method: "POST",
8603
+ url: "/tools/{toolUuid}/patterns/{patternId}/organizations/{provider}/{remoteOrganizationName}/feedback",
8604
+ path: {
8605
+ "toolUuid": toolUuid,
8606
+ "patternId": patternId,
8607
+ "provider": provider,
8608
+ "remoteOrganizationName": remoteOrganizationName
8609
+ },
8610
+ abortSignal,
8611
+ body: requestBody,
8612
+ mediaType: "application/json",
8613
+ errors: {
8614
+ 400: `Bad Request`,
8615
+ 401: `Unauthorized`,
8616
+ 404: `Not Found`,
8617
+ 422: `Unprocessable Entity`,
8618
+ 500: `Internal Server Error`
8619
+ }
8620
+ });
8621
+ }
8622
+ /**
8623
+ * Retrieve a tool pattern
8624
+ * Get the definition of a specific pattern
8625
+ * @param toolUuid Unique identifier (UUID) for the tool* @param patternId Pattern identifier * @returns PatternResponse Successful operation
8626
+ * @throws ApiError
8627
+ */
8628
+ static getPattern(toolUuid, patternId, abortSignal) {
8629
+ return request(OpenAPI, {
8630
+ method: "GET",
8631
+ url: "/tools/{toolUuid}/patterns/{patternId}",
8632
+ path: {
8633
+ "toolUuid": toolUuid,
8634
+ "patternId": patternId
7649
8635
  },
7650
8636
  abortSignal,
7651
8637
  errors: {
@@ -7753,6 +8739,8 @@ async function listRepositoryToolPatterns(provider, org, repo, toolUuid) {
7753
8739
  void 0,
7754
8740
  // recommended
7755
8741
  void 0,
8742
+ // matchesStack
8743
+ void 0,
7756
8744
  // sort
7757
8745
  void 0,
7758
8746
  // direction
@@ -7790,6 +8778,20 @@ async function listIgnoredFiles(provider, org, repo) {
7790
8778
  capped: pages >= MAX_PAGES && cursor !== void 0
7791
8779
  };
7792
8780
  }
8781
+ async function uploadImageSbom(provider, organization, upload, signal) {
8782
+ await SbomService.uploadImageSbom(
8783
+ provider,
8784
+ organization,
8785
+ {
8786
+ sbom: upload.sbom,
8787
+ imageName: upload.imageName,
8788
+ tag: upload.tag,
8789
+ ...upload.repositoryName ? { repositoryName: upload.repositoryName } : {},
8790
+ ...upload.environment ? { environment: upload.environment } : {}
8791
+ },
8792
+ signal
8793
+ );
8794
+ }
7793
8795
 
7794
8796
  // src/init.ts
7795
8797
  function isAuthFailure(err) {
@@ -7827,6 +8829,13 @@ function buildAdapterByUuid(adapters, apiTools) {
7827
8829
  }
7828
8830
  return result;
7829
8831
  }
8832
+ function listRepoFilesOrEmpty(repositoryRoot, exclude) {
8833
+ try {
8834
+ return listRepoFiles(repositoryRoot, exclude);
8835
+ } catch {
8836
+ return [];
8837
+ }
8838
+ }
7830
8839
  async function detectLanguages(repositoryRoot, precomputedFiles, exclude) {
7831
8840
  let files;
7832
8841
  if (precomputedFiles) {
@@ -7848,9 +8857,9 @@ async function detectLanguages(repositoryRoot, precomputedFiles, exclude) {
7848
8857
  }
7849
8858
  const detected = /* @__PURE__ */ new Set();
7850
8859
  for (const file of files) {
7851
- const basename = import_path8.default.basename(file);
7852
- const ext = import_path8.default.extname(file).toLowerCase();
7853
- for (const [lang, config] of Object.entries(import_tooling7.LANGUAGE_CONFIG)) {
8860
+ const basename = import_path9.default.basename(file);
8861
+ const ext = import_path9.default.extname(file).toLowerCase();
8862
+ for (const [lang, config] of Object.entries(import_tooling8.LANGUAGE_CONFIG)) {
7854
8863
  if (config.extensions.includes(ext)) {
7855
8864
  detected.add(lang);
7856
8865
  } else if (config.files.includes(basename)) {
@@ -7866,9 +8875,9 @@ async function detectLocalConfigFiles(repositoryRoot, adapters) {
7866
8875
  adapters.map(async (adapter) => {
7867
8876
  const result = await adapter.checkLocalConfigurationFile({
7868
8877
  repositoryRoot,
7869
- generatedConfigDir: import_path8.default.join(repositoryRoot, ".codacy", "generated"),
7870
- globalDir: import_path8.default.join(process.env.HOME ?? "~", ".codacy"),
7871
- toolInstallDir: import_path8.default.join(process.env.HOME ?? "~", ".codacy", "tools", adapter.id),
8878
+ generatedConfigDir: import_path9.default.join(repositoryRoot, ".codacy", "generated"),
8879
+ globalDir: (0, import_tooling8.resolveGlobalDir)(),
8880
+ toolInstallDir: import_path9.default.join((0, import_tooling8.resolveGlobalDir)(), "tools", adapter.id),
7872
8881
  targetFiles: [],
7873
8882
  platform: { os: process.platform, arch: process.arch },
7874
8883
  logger: createNullLogger(),
@@ -7894,7 +8903,7 @@ function enrichWithCodacyYaml(config, adapters, excludes) {
7894
8903
  async function initLocalConfigBase(repositoryRoot, adapters, excludes) {
7895
8904
  const languages = await detectLanguages(
7896
8905
  repositoryRoot,
7897
- listRepoFiles(repositoryRoot, excludes.global)
8906
+ listRepoFilesOrEmpty(repositoryRoot, excludes.global)
7898
8907
  );
7899
8908
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
7900
8909
  const tools = [];
@@ -8078,60 +9087,11 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
8078
9087
  }
8079
9088
  };
8080
9089
  }
8081
- async function initContainerConfig(repositoryRoot, adapters) {
8082
- const codacyrcPath = import_path8.default.join(repositoryRoot, ".codacyrc");
8083
- let raw;
8084
- try {
8085
- raw = await import_promises4.default.readFile(codacyrcPath, "utf-8");
8086
- } catch {
8087
- throw new Error(`.codacyrc not found at ${codacyrcPath}`);
8088
- }
8089
- let parsed;
8090
- try {
8091
- parsed = JSON.parse(raw);
8092
- } catch {
8093
- try {
8094
- const decoded = Buffer.from(raw.trim(), "base64").toString("utf-8");
8095
- parsed = JSON.parse(decoded);
8096
- } catch {
8097
- throw new Error(`Failed to parse .codacyrc: not valid JSON or base64-encoded JSON`);
8098
- }
8099
- }
8100
- const includeFiles = Array.isArray(parsed.files) ? parsed.files.filter((f) => typeof f === "string") : [];
8101
- const adapterById = new Map(adapters.map((a) => [a.id, a]));
8102
- const tools = [];
8103
- if (Array.isArray(parsed.tools)) {
8104
- for (const toolEntry of parsed.tools) {
8105
- if (typeof toolEntry !== "object" || toolEntry === null) continue;
8106
- const entry = toolEntry;
8107
- const toolId = typeof entry["toolId"] === "string" ? entry["toolId"] : void 0;
8108
- if (!toolId || !adapterById.has(toolId)) continue;
8109
- const patterns = [];
8110
- if (Array.isArray(entry["patterns"])) {
8111
- for (const p of entry["patterns"]) {
8112
- if (typeof p === "object" && p !== null && typeof p["patternId"] === "string") {
8113
- const pe = p;
8114
- const rawParams = pe["parameters"];
8115
- let params;
8116
- if (rawParams && typeof rawParams === "object" && !Array.isArray(rawParams)) {
8117
- params = Object.fromEntries(
8118
- Object.entries(rawParams).map(([k, v]) => [
8119
- k,
8120
- String(v)
8121
- ])
8122
- );
8123
- }
8124
- patterns.push({
8125
- patternId: pe["patternId"],
8126
- ...params ? { parameters: params } : {}
8127
- });
8128
- }
8129
- }
8130
- }
8131
- tools.push({ toolId, patterns });
8132
- }
8133
- }
8134
- return createCodacyConfig({ source: "container", tools, includeFiles });
9090
+ async function initContainerConfig(repositoryRoot, adapters, codacyrcPath) {
9091
+ const resolvedPath = codacyrcPath ?? import_path9.default.join(repositoryRoot, ".codacyrc");
9092
+ const codacyrc = await readCodacyrc(resolvedPath);
9093
+ const { config } = await resolveCodacyrc(codacyrc, { toolIds: adapters.map((a) => a.id) });
9094
+ return config;
8135
9095
  }
8136
9096
  function hasMatchingFiles(files, filePatterns) {
8137
9097
  const matcher = new CompiledMatcher(filePatterns);
@@ -8147,12 +9107,12 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
8147
9107
  const discoveryResult = await discover(repositoryRoot, { exclude: excludes.global });
8148
9108
  onDiscoveryComplete?.(discoveryResult);
8149
9109
  const detectedKeys = /* @__PURE__ */ new Set();
8150
- for (const lang of discoveryResult.languages) detectedKeys.add((0, import_tooling7.languageKey)(lang));
9110
+ for (const lang of discoveryResult.languages) detectedKeys.add((0, import_tooling8.languageKey)(lang));
8151
9111
  for (const fw of discoveryResult.frameworks) detectedKeys.add(fw.key);
8152
9112
  for (const lib of discoveryResult.libraries) detectedKeys.add(lib.key);
8153
9113
  const repoLanguages = new Set(discoveryResult.languages);
8154
9114
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
8155
- const repoFiles = listRepoFiles(repositoryRoot, excludes.global);
9115
+ const repoFiles = listRepoFilesOrEmpty(repositoryRoot, excludes.global);
8156
9116
  const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
8157
9117
  const perToolFiles = /* @__PURE__ */ new Map();
8158
9118
  const filesVisibleTo = (toolId) => {
@@ -8279,12 +9239,12 @@ async function applyAdapterConfigSuggestions(adapter, toolEntry, repositoryRoot,
8279
9239
  }
8280
9240
  };
8281
9241
  try {
8282
- const globalDir = import_path8.default.join(import_os3.default.homedir(), ".codacy");
9242
+ const globalDir = (0, import_tooling8.resolveGlobalDir)();
8283
9243
  const ctx = {
8284
9244
  repositoryRoot,
8285
- generatedConfigDir: import_path8.default.join(repositoryRoot, ".codacy", "generated", adapter.id),
9245
+ generatedConfigDir: import_path9.default.join(repositoryRoot, ".codacy", "generated", adapter.id),
8286
9246
  globalDir,
8287
- toolInstallDir: import_path8.default.join(globalDir, "tools", adapter.id),
9247
+ toolInstallDir: import_path9.default.join(globalDir, "tools", adapter.id),
8288
9248
  targetFiles: routeFilesToTool(repoFiles, adapter, toolEntry),
8289
9249
  platform: { os: process.platform, arch: process.arch },
8290
9250
  logger,
@@ -8545,15 +9505,15 @@ function parseAutoFilters(raw) {
8545
9505
  }
8546
9506
 
8547
9507
  // src/index.ts
8548
- var import_tooling8 = require("@codacy/tooling");
9508
+ var import_tooling10 = require("@codacy/tooling");
8549
9509
 
8550
9510
  // src/credentials.ts
8551
9511
  var import_crypto = __toESM(require("crypto"));
8552
9512
  var import_promises5 = __toESM(require("fs/promises"));
8553
9513
  var import_os4 = __toESM(require("os"));
8554
- var import_path9 = __toESM(require("path"));
9514
+ var import_path10 = __toESM(require("path"));
8555
9515
  function credentialsPath() {
8556
- return import_path9.default.join(import_os4.default.homedir(), ".codacy", "credentials");
9516
+ return import_path10.default.join(import_os4.default.homedir(), ".codacy", "credentials");
8557
9517
  }
8558
9518
  function deriveKey(salt) {
8559
9519
  const info = import_os4.default.userInfo();
@@ -8574,7 +9534,7 @@ async function saveCredentials(token) {
8574
9534
  authTag: authTag.toString("hex"),
8575
9535
  encrypted: encrypted.toString("hex")
8576
9536
  });
8577
- await import_promises5.default.mkdir(import_path9.default.dirname(credentialsFile), { recursive: true });
9537
+ await import_promises5.default.mkdir(import_path10.default.dirname(credentialsFile), { recursive: true });
8578
9538
  await import_promises5.default.writeFile(credentialsFile, payload, { encoding: "utf-8", mode: 384 });
8579
9539
  }
8580
9540
  async function loadCredentials() {
@@ -8875,11 +9835,287 @@ async function safeReadBody(res) {
8875
9835
  return "";
8876
9836
  }
8877
9837
  }
9838
+
9839
+ // src/container-image.ts
9840
+ var import_promises7 = __toESM(require("fs/promises"));
9841
+ var import_os5 = __toESM(require("os"));
9842
+ var import_path11 = __toESM(require("path"));
9843
+ var import_tooling9 = require("@codacy/tooling");
9844
+ var DEFAULT_IMAGE_SCAN_SEVERITIES = ["HIGH", "CRITICAL"];
9845
+ var DEFAULT_IMAGE_SCAN_PKG_TYPES = ["os"];
9846
+ var SBOM_UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
9847
+ var PROVIDERS = ["gh", "gl", "bb"];
9848
+ async function scanContainerImage(options) {
9849
+ const startedAt = /* @__PURE__ */ new Date();
9850
+ (0, import_tooling9.validateImageReference)(options.image);
9851
+ const severities = parseVocabulary(
9852
+ options.severities ?? DEFAULT_IMAGE_SCAN_SEVERITIES,
9853
+ import_tooling9.IMAGE_SCAN_SEVERITIES,
9854
+ "severity",
9855
+ (v) => v.toUpperCase()
9856
+ );
9857
+ const pkgTypes = parseVocabulary(
9858
+ options.pkgTypes ?? DEFAULT_IMAGE_SCAN_PKG_TYPES,
9859
+ import_tooling9.IMAGE_SCAN_PKG_TYPES,
9860
+ "package type",
9861
+ (v) => v.toLowerCase()
9862
+ );
9863
+ if (options.signal?.aborted) {
9864
+ return imageResult(startedAt, emptyCapability(), [], [cancelledError("before it started")], []);
9865
+ }
9866
+ const logger = options.logger ?? createNullLogger();
9867
+ const adapter = await resolveImageAdapter();
9868
+ const baseCtx = makeBaseContext(logger, options.signal);
9869
+ const prepared = await prepareTool(adapter, baseCtx, logger);
9870
+ if (!prepared.ok) {
9871
+ return imageResult(startedAt, prepared.capability, [], [prepared.error], []);
9872
+ }
9873
+ if (options.signal?.aborted) {
9874
+ return imageResult(startedAt, prepared.capability, [], [cancelledError("before the scan")], []);
9875
+ }
9876
+ const run = await adapter.containerImage.scan(makeToolContext(baseCtx, adapter, []), {
9877
+ image: options.image,
9878
+ severities,
9879
+ pkgTypes,
9880
+ ignoreUnfixed: options.ignoreUnfixed ?? true
9881
+ });
9882
+ const lookup = /* @__PURE__ */ new Map();
9883
+ for (const entry of adapter.patternsMetadata ?? []) {
9884
+ lookup.set(`${adapter.id}|${entry.id}`, {
9885
+ title: entry.title,
9886
+ toolDisplayName: adapter.displayName
9887
+ });
9888
+ }
9889
+ const patternSummary = run.issues.length > 0 ? computePatternSummary(run.issues, lookup) : void 0;
9890
+ return imageResult(
9891
+ startedAt,
9892
+ prepared.capability,
9893
+ run.issues,
9894
+ run.errors,
9895
+ [run.summary],
9896
+ patternSummary
9897
+ );
9898
+ }
9899
+ async function uploadImageSbom2(options) {
9900
+ const target = (0, import_tooling9.resolveImageTarget)(options.image, options.tag);
9901
+ const format = parseSbomFormat(options.format);
9902
+ if (!PROVIDERS.includes(options.provider)) {
9903
+ throw new Error(
9904
+ `Invalid provider "${options.provider}": expected one of ${PROVIDERS.join(", ")}`
9905
+ );
9906
+ }
9907
+ assertValidCoordinate("organization", options.organization);
9908
+ if (options.repositoryName) assertValidCoordinate("repository", options.repositoryName);
9909
+ if (!options.apiToken) {
9910
+ throw new Error("An account API token is required to upload an SBOM.");
9911
+ }
9912
+ throwIfCancelled(options.signal, "before it started");
9913
+ const logger = options.logger ?? createNullLogger();
9914
+ const progress = options.onProgress ?? (() => {
9915
+ });
9916
+ const adapter = await resolveImageAdapter();
9917
+ const baseCtx = makeBaseContext(logger, options.signal);
9918
+ const prepared = await prepareTool(adapter, baseCtx, logger);
9919
+ if (!prepared.ok) {
9920
+ throw new Error(`Could not install ${adapter.displayName}: ${prepared.error.message}`);
9921
+ }
9922
+ throwIfCancelled(options.signal, "before SBOM generation");
9923
+ const tempDir = await import_promises7.default.mkdtemp(import_path11.default.join(import_os5.default.tmpdir(), "codacy-sbom-"));
9924
+ try {
9925
+ const outputPath = import_path11.default.join(
9926
+ tempDir,
9927
+ format === "cyclonedx" ? "sbom.cdx.json" : "sbom.spdx.json"
9928
+ );
9929
+ progress(`Generating SBOM for image: ${target.scanRef}`);
9930
+ const generated = await adapter.containerImage.generateSbom(
9931
+ makeToolContext(baseCtx, adapter, []),
9932
+ { image: target.scanRef, format, outputPath }
9933
+ );
9934
+ if (!generated.success) {
9935
+ throw new Error(`Failed to generate SBOM: ${generated.error?.message ?? "unknown error"}`);
9936
+ }
9937
+ const contents = await import_promises7.default.readFile(outputPath);
9938
+ const sbom = new File([contents], import_path11.default.basename(outputPath), { type: "application/json" });
9939
+ throwIfCancelled(options.signal, "before the upload");
9940
+ progress(`Uploading SBOM to Codacy (org: ${options.provider}/${options.organization})...`);
9941
+ configureApiToken({ kind: "account-token", token: options.apiToken });
9942
+ const timeout = AbortSignal.timeout(SBOM_UPLOAD_TIMEOUT_MS);
9943
+ try {
9944
+ await uploadImageSbom(
9945
+ options.provider,
9946
+ options.organization,
9947
+ {
9948
+ sbom,
9949
+ imageName: target.name,
9950
+ tag: target.tag,
9951
+ repositoryName: options.repositoryName,
9952
+ environment: options.environment
9953
+ },
9954
+ options.signal ? AbortSignal.any([options.signal, timeout]) : timeout
9955
+ );
9956
+ } catch (err) {
9957
+ throw new Error(`Failed to upload SBOM: ${describeUploadError(err)}`);
9958
+ }
9959
+ return { imageName: target.name, tag: target.tag, format };
9960
+ } finally {
9961
+ await import_promises7.default.rm(tempDir, { recursive: true, force: true });
9962
+ }
9963
+ }
9964
+ function parseVocabulary(values, allowed, label, normalize) {
9965
+ const parsed = values.flatMap((v) => v.split(",")).map((v) => normalize(v.trim())).filter((v) => v.length > 0);
9966
+ if (parsed.length === 0) {
9967
+ throw new Error(`At least one ${label} is required (accepted: ${allowed.join(", ")})`);
9968
+ }
9969
+ for (const value of parsed) {
9970
+ if (!allowed.includes(value)) {
9971
+ throw new Error(`Invalid ${label} "${value}": expected one of ${allowed.join(", ")}`);
9972
+ }
9973
+ }
9974
+ return [...new Set(parsed)];
9975
+ }
9976
+ function parseSbomFormat(format) {
9977
+ const value = format ?? import_tooling9.SBOM_FORMATS[0];
9978
+ if (!import_tooling9.SBOM_FORMATS.includes(value)) {
9979
+ throw new Error(`Invalid format "${value}": expected one of ${import_tooling9.SBOM_FORMATS.join(", ")}`);
9980
+ }
9981
+ return value;
9982
+ }
9983
+ async function resolveImageAdapter() {
9984
+ const descriptor = getRegisteredDescriptors().find(
9985
+ (d) => d.scansContainerImages && !d.unsupported
9986
+ );
9987
+ const adapter = descriptor ? await getAdapter(descriptor.id) : void 0;
9988
+ if (!adapter?.containerImage) {
9989
+ throw new Error(
9990
+ "No registered tool can scan container images. Call registerBuiltinAdapters() first."
9991
+ );
9992
+ }
9993
+ return adapter;
9994
+ }
9995
+ function makeBaseContext(logger, signal) {
9996
+ return {
9997
+ repositoryRoot: process.cwd(),
9998
+ globalDir: (0, import_tooling9.resolveGlobalDir)(),
9999
+ platform: { os: import_os5.default.platform(), arch: import_os5.default.arch() },
10000
+ logger,
10001
+ runner: "local",
10002
+ ...signal ? { signal } : {}
10003
+ };
10004
+ }
10005
+ async function prepareTool(adapter, baseCtx, logger) {
10006
+ const ctx = makeToolContext(baseCtx, adapter, []);
10007
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
10008
+ const availability = await adapter.checkAvailability(ctx);
10009
+ if (availability.available) {
10010
+ const status = {
10011
+ toolId: adapter.id,
10012
+ displayName: adapter.displayName,
10013
+ version: availability.version ?? adapter.preferredVersion,
10014
+ installation: availability.installation ?? "global",
10015
+ runner: "local"
10016
+ };
10017
+ const capability = { ready: [status], unavailable: [], checkedAt };
10018
+ await updateStaleManagedTools(capability, [adapter], baseCtx, logger);
10019
+ return { ok: true, capability };
10020
+ }
10021
+ const outcome = await installMissingTool(adapter, ctx, logger);
10022
+ if (outcome.kind === "installed") {
10023
+ return {
10024
+ ok: true,
10025
+ capability: {
10026
+ ready: [
10027
+ {
10028
+ toolId: adapter.id,
10029
+ displayName: adapter.displayName,
10030
+ version: outcome.version,
10031
+ installation: outcome.installation,
10032
+ runner: "local"
10033
+ }
10034
+ ],
10035
+ unavailable: [],
10036
+ checkedAt
10037
+ }
10038
+ };
10039
+ }
10040
+ const error = outcome.kind === "failed" ? outcome.error : {
10041
+ toolId: adapter.id,
10042
+ phase: "toolInstall",
10043
+ kind: "NotInstalled",
10044
+ message: `${adapter.displayName} is not installed and cannot be installed automatically: ${availability.reason ?? "not found"}`,
10045
+ level: "error"
10046
+ };
10047
+ return {
10048
+ ok: false,
10049
+ error,
10050
+ capability: {
10051
+ ready: [],
10052
+ unavailable: [
10053
+ {
10054
+ toolId: adapter.id,
10055
+ displayName: adapter.displayName,
10056
+ reason: error.message,
10057
+ ...availability.installHint ? { remediation: availability.installHint } : {}
10058
+ }
10059
+ ],
10060
+ checkedAt
10061
+ }
10062
+ };
10063
+ }
10064
+ function emptyCapability() {
10065
+ return { ready: [], unavailable: [], checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
10066
+ }
10067
+ function cancelledError(when) {
10068
+ return {
10069
+ toolId: "",
10070
+ phase: "toolInvoke",
10071
+ kind: "Cancelled",
10072
+ message: `Container image scan cancelled by caller ${when}`,
10073
+ level: "error"
10074
+ };
10075
+ }
10076
+ function throwIfCancelled(signal, when) {
10077
+ if (signal?.aborted) throw new Error(`SBOM upload cancelled by caller ${when}`);
10078
+ }
10079
+ function imageResult(startedAt, capability, issues, errors, toolResults, patternSummary) {
10080
+ const completedAt = /* @__PURE__ */ new Date();
10081
+ return {
10082
+ metadata: {
10083
+ startedAt: startedAt.toISOString(),
10084
+ completedAt: completedAt.toISOString(),
10085
+ durationMs: completedAt.getTime() - startedAt.getTime(),
10086
+ repositoryRoot: process.cwd(),
10087
+ executionMode: "auto-install"
10088
+ },
10089
+ capability,
10090
+ issues,
10091
+ errors,
10092
+ toolResults,
10093
+ ...patternSummary ? { patternSummary } : {}
10094
+ };
10095
+ }
10096
+ function describeUploadError(err) {
10097
+ if (err instanceof Error && err.name === "AbortError") {
10098
+ return "cancelled by caller";
10099
+ }
10100
+ if (err instanceof Error && err.name === "TimeoutError") {
10101
+ return `timed out after ${SBOM_UPLOAD_TIMEOUT_MS / 6e4} minutes`;
10102
+ }
10103
+ const apiError = err;
10104
+ if (typeof apiError?.status === "number") {
10105
+ const body = typeof apiError.body === "string" ? apiError.body : JSON.stringify(apiError.body ?? "");
10106
+ return `API returned status ${apiError.status}${body && body !== '""' ? `: ${body.slice(0, 500)}` : ""}`;
10107
+ }
10108
+ return err instanceof Error ? err.message : String(err);
10109
+ }
8878
10110
  // Annotate the CommonJS export names for ESM import in node:
8879
10111
  0 && (module.exports = {
10112
+ DEFAULT_IMAGE_SCAN_PKG_TYPES,
10113
+ DEFAULT_IMAGE_SCAN_SEVERITIES,
8880
10114
  analyze,
8881
10115
  applyCodacyYamlExcludes,
8882
10116
  applyConfigOperation,
10117
+ buildCodacyrc,
10118
+ buildPatternTranslator,
8883
10119
  buildPayloads,
8884
10120
  buildToolConfig,
8885
10121
  clearAdapters,
@@ -8904,6 +10140,7 @@ async function safeReadBody(res) {
8904
10140
  initDefaultConfig,
8905
10141
  initLocalConfig,
8906
10142
  initRemoteConfig,
10143
+ installTools,
8907
10144
  intersectConfigs,
8908
10145
  listDiffFiles,
8909
10146
  listIgnoredFiles,
@@ -8912,17 +10149,22 @@ async function safeReadBody(res) {
8912
10149
  loadCredentials,
8913
10150
  mergeConfigs,
8914
10151
  parseAutoFilters,
10152
+ parseCodacyrc,
8915
10153
  parseReport,
10154
+ partitionContainerErrors,
8916
10155
  pruneEmptyTools,
8917
10156
  readBaselineConfig,
8918
10157
  readCodacyConfig,
8919
10158
  readCodacyYaml,
10159
+ readCodacyrc,
8920
10160
  registerAdapter,
8921
10161
  registerLazyAdapter,
8922
10162
  registerUnsupportedTool,
10163
+ registryLookup,
8923
10164
  relativizeLocalConfigPaths,
8924
10165
  resolveApiToken,
8925
10166
  resolveBaselinePath,
10167
+ resolveCodacyrc,
8926
10168
  resolveConfigPath,
8927
10169
  resolveDiscoveryExcludes,
8928
10170
  resolveEngineToAdapter,
@@ -8932,9 +10174,14 @@ async function safeReadBody(res) {
8932
10174
  resolveRemoteAuth,
8933
10175
  resolveRepositoryRoot,
8934
10176
  resolveResultsApiBaseUrl,
10177
+ resolveToolName,
8935
10178
  rotateLogFiles,
8936
10179
  saveCredentials,
10180
+ scanContainerImage,
10181
+ serializeCodacyrc,
10182
+ toEnginePatternId,
8937
10183
  updateConfigIncremental,
10184
+ uploadImageSbom,
8938
10185
  uploadReport,
8939
10186
  validateApiToken,
8940
10187
  writeBaselineConfig,