@codacy/analysis-runner 0.15.0 → 0.17.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.
package/dist/index.d.ts CHANGED
@@ -586,7 +586,12 @@ interface InitAutoStats {
586
586
  *
587
587
  * Severity and category filters are ANDed across dimensions, ORed within.
588
588
  *
589
- * **Known limitation (v1):** When both TypeScript and JavaScript are detected,
589
+ * Tool-level overlap (two *different* tools that lint the same stack, e.g.
590
+ * ESLint and Biome) is resolved by `deduplicateToolFamilies` via each
591
+ * descriptor's `alternativeGroup` — only one member of a group is selected.
592
+ *
593
+ * **Known limitation (v1):** *Pattern*-level overlap within a single tool is
594
+ * not yet resolved — e.g. when both TypeScript and JavaScript are detected,
590
595
  * ESLint core rules and their `@typescript-eslint` equivalents may both be
591
596
  * enabled, potentially causing duplicate findings. This will be addressed in v2
592
597
  * via conflict-resolution metadata in the mapping files.
@@ -853,4 +858,174 @@ declare function discover(repositoryRoot: string, options?: DiscoverOptions): Pr
853
858
  */
854
859
  declare function getKnownDisplayNames(): Set<string>;
855
860
 
856
- export { type AdapterFactory, type AnalyzeOptions, type AutoFilters, type CategoryFilter, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ConfigOperation, type DiscoverOptions, type ExecutionMode, type IgnoredFilesResult, type InitAutoStats, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, type MergeOptions, type PatternMetadataLookup, type SeverityFilter, analyze, applyCodacyYamlExcludes, applyConfigOperation, buildToolConfig, clearAdapters, computePatternSummary, configureApiToken, createCodacyConfig, createLogger, createNullLogger, deleteCredentials, detectLanguages, diffConfigs, discover, formatOutput, formatTextSummary, getAdapter, getKnownDisplayNames, getRegisteredAdapters, getRegisteredDescriptors, initAutoConfig, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, intersectConfigs, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, loadCredentials, mergeConfigs, parseAutoFilters, pruneEmptyTools, readBaselineConfig, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveBaselinePath, resolveConfigPath, resolveEngineToAdapter, rotateLogFiles, saveCredentials, updateConfigIncremental, validateApiToken, writeBaselineConfig, writeCodacyConfig, writeCodacyConfigFile };
861
+ /**
862
+ * Git repository introspection helpers used by commands that need to know
863
+ * *where* they are (repository root) and *what* they are looking at (the current
864
+ * commit SHA). Kept separate from `git-scope.ts` (which resolves file *sets* for
865
+ * `--staged`/`--diff`/`--pr`); this module answers point questions about the repo.
866
+ *
867
+ * Both helpers shell out with `execFileSync` (never a shell string) so paths and
868
+ * refs can't be interpreted by the shell.
869
+ */
870
+ /**
871
+ * Resolves the absolute path to the enclosing git repository's root.
872
+ *
873
+ * Wraps `git rev-parse --show-toplevel`. Throws a clear error when `cwd` is not
874
+ * inside a git working tree, so callers can surface an actionable message rather
875
+ * than a raw git failure.
876
+ */
877
+ declare function resolveRepositoryRoot(cwd?: string): string;
878
+ /**
879
+ * Resolves the SHA of the current `HEAD` commit.
880
+ *
881
+ * Wraps `git rev-parse HEAD`. Used by `upload` to auto-detect the commit when the
882
+ * caller omits `--commit`. Throws when there is no commit yet (empty repo) or the
883
+ * directory is not a git repository, so the command can tell the user to pass
884
+ * `--commit` explicitly.
885
+ */
886
+ declare function resolveHeadCommit(repositoryRoot: string): string;
887
+
888
+ /**
889
+ * `upload` — sends an analysis report produced by this CLI to Codacy, attaching
890
+ * its issues to a commit so they surface on Codacy Cloud.
891
+ *
892
+ * ## What it uploads
893
+ *
894
+ * Only reports this CLI produced, in either output format:
895
+ * - `sarif` — our SARIF v2.1.0 document, where `run.tool.driver.name` is the tool
896
+ * id and each `result.ruleId` is already a Codacy pattern id.
897
+ * - `json` — the full `AnalysisResult`, whose `issues[]` carry `toolId`, `patternId`,
898
+ * `severity`, etc.
899
+ *
900
+ * Because our reports already carry Codacy-native pattern ids, no online pattern
901
+ * lookup is needed (unlike the legacy Go CLI, which had to map raw tool rule ids to
902
+ * Codacy patterns). We normalize either format into a flat issue list and build the
903
+ * payload directly.
904
+ *
905
+ * ## Where it uploads
906
+ *
907
+ * The **legacy v2 API** (`https://api.codacy.com/2.0`), matching the Go CLI: one
908
+ * `issuesRemoteResults` POST per tool, then a single `resultsFinal` POST to close the
909
+ * commit. The backend stores the `issues` payload as an opaque blob and a separate
910
+ * downstream engine parses it, so the payload shape must match the Go CLI's exactly.
911
+ *
912
+ * ## Auth
913
+ *
914
+ * Two mutually exclusive modes (see {@link UploadAuth}): a repository token
915
+ * (`project-token` header, commit-scoped URL) or an account token (`api-token`
916
+ * header, provider/owner/repo-scoped URL).
917
+ */
918
+ /**
919
+ * An issue flattened from either report format into the minimal fields the v2
920
+ * payload needs. `filePath` is relative to the repository root (both our SARIF and
921
+ * JSON emit repo-relative paths, so no recomputation is required).
922
+ */
923
+ interface NormalizedIssue {
924
+ /** Tool id (our adapter id, e.g. "ESLint9"); becomes the payload's tool grouping. */
925
+ toolId: string;
926
+ /** Codacy pattern id (e.g. "ESLint9_accessor-pairs"). */
927
+ patternId: string;
928
+ /** Repository-relative file path. */
929
+ filePath: string;
930
+ /** 1-based line number. */
931
+ line: number;
932
+ /** Issue message text. */
933
+ message: string;
934
+ /** Codacy level string (Info/Warning/High/Error for json; Info/Warning/Error for sarif). */
935
+ level: string;
936
+ }
937
+ /** Authentication + routing for the upload. */
938
+ type UploadAuth = {
939
+ kind: "repository-token";
940
+ /** Repository (a.k.a. project) token; sent as the `project-token` header. */
941
+ token: string;
942
+ } | {
943
+ kind: "account-token";
944
+ /** Account/user token; sent as the `api-token` header. */
945
+ token: string;
946
+ provider: string;
947
+ owner: string;
948
+ repository: string;
949
+ };
950
+ /** Options for {@link uploadReport}. */
951
+ interface UploadOptions {
952
+ /** Absolute or CWD-relative path to the report file (sarif or json). */
953
+ reportPath: string;
954
+ /** Commit SHA/UUID the results attach to. */
955
+ commitUuid: string;
956
+ auth: UploadAuth;
957
+ /** Progress callback for human-facing messages (stderr in the CLI). */
958
+ onProgress?: (message: string) => void;
959
+ }
960
+ /** Outcome of an upload. */
961
+ interface UploadResult {
962
+ /** Tool short-names that had at least one issue uploaded. */
963
+ toolsUploaded: string[];
964
+ /** Total issues uploaded across all tools. */
965
+ issueCount: number;
966
+ /** Whether the final `resultsFinal` call succeeded. */
967
+ finalized: boolean;
968
+ }
969
+ /** The per-tool payload object (wrapped in a one-element array per request). */
970
+ interface ToolPayload {
971
+ tool: string;
972
+ issues: {
973
+ Success: {
974
+ results: FileResults[];
975
+ };
976
+ };
977
+ }
978
+ interface FileResults {
979
+ filename: string;
980
+ results: Array<{
981
+ Issue: PayloadIssue;
982
+ }>;
983
+ }
984
+ interface PayloadIssue {
985
+ patternId: {
986
+ value: string;
987
+ };
988
+ filename: string;
989
+ message: {
990
+ text: string;
991
+ };
992
+ level: string;
993
+ location: {
994
+ LineLocation: {
995
+ line: number;
996
+ };
997
+ };
998
+ }
999
+ /**
1000
+ * Resolves the base URL for the legacy v2 results API. Defaults to Codacy SaaS;
1001
+ * override with `CODACY_RESULTS_API_BASE_URL` for self-hosted deployments. Note this
1002
+ * is a different host from `CODACY_API_BASE_URL` (the v3 app API).
1003
+ */
1004
+ declare function resolveResultsApiBaseUrl(): string;
1005
+ /**
1006
+ * Detects the report format and flattens it into {@link NormalizedIssue}s.
1007
+ * Accepts only reports produced by this CLI (our SARIF or our JSON `AnalysisResult`);
1008
+ * throws a clear error otherwise.
1009
+ */
1010
+ declare function parseReport(fileContents: string): NormalizedIssue[];
1011
+ /**
1012
+ * Builds one v2 payload per tool, grouping issues by file. `resolveShortName` maps
1013
+ * our internal tool id to the Codacy tool short-name expected in the `tool` field
1014
+ * (which becomes an S3 key segment consumed downstream).
1015
+ */
1016
+ declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string): ToolPayload[];
1017
+ /**
1018
+ * Uploads an analysis report to Codacy. Reads and parses the report, resolves tool
1019
+ * short-names, POSTs one `issuesRemoteResults` per tool, then a single `resultsFinal`.
1020
+ *
1021
+ * Throws on a failed `issuesRemoteResults` POST (partial uploads shouldn't be
1022
+ * silently finalized). A failed `resultsFinal` is surfaced via `onProgress` and
1023
+ * reflected in the returned {@link UploadResult}, matching the Go CLI's non-fatal
1024
+ * treatment of finalization.
1025
+ *
1026
+ * Proxy/TLS is honored automatically — all `fetch` traffic goes through the global
1027
+ * dispatcher installed by `configureProxy()`.
1028
+ */
1029
+ declare function uploadReport(options: UploadOptions): Promise<UploadResult>;
1030
+
1031
+ export { type AdapterFactory, type AnalyzeOptions, type AutoFilters, type CategoryFilter, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ConfigOperation, type DiscoverOptions, type ExecutionMode, type IgnoredFilesResult, type InitAutoStats, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, type MergeOptions, type NormalizedIssue, type PatternMetadataLookup, type SeverityFilter, type UploadAuth, type UploadOptions, type UploadResult, analyze, applyCodacyYamlExcludes, applyConfigOperation, buildPayloads, buildToolConfig, clearAdapters, computePatternSummary, configureApiToken, createCodacyConfig, createLogger, createNullLogger, deleteCredentials, detectLanguages, diffConfigs, discover, formatOutput, formatTextSummary, getAdapter, getKnownDisplayNames, getRegisteredAdapters, getRegisteredDescriptors, initAutoConfig, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, intersectConfigs, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, loadCredentials, mergeConfigs, parseAutoFilters, parseReport, pruneEmptyTools, readBaselineConfig, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveBaselinePath, resolveConfigPath, resolveEngineToAdapter, resolveHeadCommit, resolveRepositoryRoot, resolveResultsApiBaseUrl, rotateLogFiles, saveCredentials, updateConfigIncremental, uploadReport, validateApiToken, writeBaselineConfig, writeCodacyConfig, writeCodacyConfigFile };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  analyze: () => analyze,
34
34
  applyCodacyYamlExcludes: () => applyCodacyYamlExcludes,
35
35
  applyConfigOperation: () => applyConfigOperation,
36
+ buildPayloads: () => buildPayloads,
36
37
  buildToolConfig: () => buildToolConfig,
37
38
  clearAdapters: () => clearAdapters,
38
39
  computePatternSummary: () => computePatternSummary,
@@ -64,6 +65,7 @@ __export(index_exports, {
64
65
  loadCredentials: () => loadCredentials,
65
66
  mergeConfigs: () => mergeConfigs,
66
67
  parseAutoFilters: () => parseAutoFilters,
68
+ parseReport: () => parseReport,
67
69
  pruneEmptyTools: () => pruneEmptyTools,
68
70
  readBaselineConfig: () => readBaselineConfig,
69
71
  readCodacyConfig: () => readCodacyConfig,
@@ -75,10 +77,14 @@ __export(index_exports, {
75
77
  resolveBaselinePath: () => resolveBaselinePath,
76
78
  resolveConfigPath: () => resolveConfigPath,
77
79
  resolveEngineToAdapter: () => resolveEngineToAdapter,
80
+ resolveHeadCommit: () => resolveHeadCommit,
78
81
  resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
82
+ resolveRepositoryRoot: () => resolveRepositoryRoot,
83
+ resolveResultsApiBaseUrl: () => resolveResultsApiBaseUrl,
79
84
  rotateLogFiles: () => rotateLogFiles,
80
85
  saveCredentials: () => saveCredentials,
81
86
  updateConfigIncremental: () => updateConfigIncremental,
87
+ uploadReport: () => uploadReport,
82
88
  validateApiToken: () => validateApiToken,
83
89
  writeBaselineConfig: () => writeBaselineConfig,
84
90
  writeCodacyConfig: () => writeCodacyConfig,
@@ -2314,6 +2320,7 @@ function updateConfigIncremental(base, current, next) {
2314
2320
 
2315
2321
  // src/init.ts
2316
2322
  var import_promises4 = __toESM(require("fs/promises"));
2323
+ var import_os3 = __toESM(require("os"));
2317
2324
  var import_path8 = __toESM(require("path"));
2318
2325
  var import_child_process4 = require("child_process");
2319
2326
  var import_tooling5 = require("@codacy/tooling");
@@ -7362,21 +7369,25 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7362
7369
  filters
7363
7370
  );
7364
7371
  if (selectedPatterns.length > 0) {
7365
- tools.push({
7372
+ const toolEntry = {
7366
7373
  toolId: adapter.id,
7367
7374
  patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
7368
- });
7375
+ };
7376
+ tools.push(toolEntry);
7377
+ await applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles);
7369
7378
  }
7370
7379
  }
7371
7380
  let selectedUnsupported = 0;
7372
- const selectedFamilies = new Set(tools.map((t) => toolFamily(t.toolId)));
7381
+ const selectedFamilies = new Set(
7382
+ tools.map((t) => toolGroupKey(t.toolId, descriptorMap.get(t.toolId)?.alternativeGroup))
7383
+ );
7373
7384
  for (const desc of descriptors) {
7374
7385
  if (!desc.unsupported) continue;
7375
7386
  if (!loadUnsupportedPatterns) break;
7376
7387
  if (!desc.languages.includes("*") && desc.languages.length > 0 && repoLanguages.size > 0 && !desc.languages.some((lang) => repoLanguages.has(lang))) {
7377
7388
  continue;
7378
7389
  }
7379
- const family = toolFamily(desc.id);
7390
+ const family = toolGroupKey(desc.id, desc.alternativeGroup);
7380
7391
  if (selectedFamilies.has(family)) continue;
7381
7392
  if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
7382
7393
  const patternsMetadata = loadUnsupportedPatterns(desc.id);
@@ -7420,16 +7431,58 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7420
7431
  }
7421
7432
  };
7422
7433
  }
7434
+ async function applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles) {
7435
+ if (!adapter.suggestParameters) return;
7436
+ const logger = {
7437
+ debug() {
7438
+ },
7439
+ info() {
7440
+ },
7441
+ warn(message, fields) {
7442
+ console.error(`[init --auto] ${adapter.id}: ${message}`, fields ?? "");
7443
+ },
7444
+ error(message, fields) {
7445
+ console.error(`[init --auto] ${adapter.id}: ${message}`, fields ?? "");
7446
+ }
7447
+ };
7448
+ try {
7449
+ const globalDir = import_path8.default.join(import_os3.default.homedir(), ".codacy");
7450
+ const ctx = {
7451
+ repositoryRoot,
7452
+ generatedConfigDir: import_path8.default.join(repositoryRoot, ".codacy", "generated", adapter.id),
7453
+ globalDir,
7454
+ toolInstallDir: import_path8.default.join(globalDir, "tools", adapter.id),
7455
+ targetFiles: routeFilesToTool(repoFiles, adapter, toolEntry),
7456
+ platform: { os: process.platform, arch: process.arch },
7457
+ logger,
7458
+ runner: "local"
7459
+ };
7460
+ const suggestions = await adapter.suggestParameters(ctx);
7461
+ if (!suggestions) return;
7462
+ for (const pattern of toolEntry.patterns) {
7463
+ const params = suggestions[pattern.patternId];
7464
+ if (!params) continue;
7465
+ pattern.parameters = { ...pattern.parameters, ...params };
7466
+ }
7467
+ } catch (err) {
7468
+ logger.error("suggestParameters failed", {
7469
+ error: err instanceof Error ? err.message : String(err)
7470
+ });
7471
+ }
7472
+ }
7423
7473
  function toolFamily(id) {
7424
7474
  return id.replace(/\d+$/, "");
7425
7475
  }
7476
+ function toolGroupKey(id, alternativeGroup) {
7477
+ return alternativeGroup ?? toolFamily(id);
7478
+ }
7426
7479
  function deduplicateToolFamilies(candidates, localConfigs) {
7427
7480
  const families = /* @__PURE__ */ new Map();
7428
7481
  for (const adapter of candidates) {
7429
- const family = toolFamily(adapter.id);
7430
- const group = families.get(family) ?? [];
7482
+ const key = toolGroupKey(adapter.id, adapter.alternativeGroup);
7483
+ const group = families.get(key) ?? [];
7431
7484
  group.push(adapter);
7432
- families.set(family, group);
7485
+ families.set(key, group);
7433
7486
  }
7434
7487
  const result = [];
7435
7488
  for (const group of families.values()) {
@@ -7438,16 +7491,28 @@ function deduplicateToolFamilies(candidates, localConfigs) {
7438
7491
  continue;
7439
7492
  }
7440
7493
  const withConfig = group.filter((a) => localConfigs.has(a.id));
7441
- if (withConfig.length === 1) {
7442
- result.push(withConfig[0]);
7494
+ const pool = withConfig.length > 0 ? withConfig : group;
7495
+ if (pool.length === 1) {
7496
+ result.push(pool[0]);
7443
7497
  continue;
7444
7498
  }
7445
- group.sort((a, b) => {
7446
- const majorA = parseInt(a.preferredVersion.split(".")[0], 10) || 0;
7447
- const majorB = parseInt(b.preferredVersion.split(".")[0], 10) || 0;
7448
- return majorB - majorA;
7449
- });
7450
- result.push(group[0]);
7499
+ const preferred = pool.filter((a) => a.alternativeGroupDefault);
7500
+ if (preferred.length === 1) {
7501
+ result.push(preferred[0]);
7502
+ continue;
7503
+ }
7504
+ const defaultFamilies = new Set(
7505
+ group.filter((a) => a.alternativeGroupDefault).map((a) => toolFamily(a.id))
7506
+ );
7507
+ const defaultFamilyMembers = defaultFamilies.size === 1 ? pool.filter((a) => defaultFamilies.has(toolFamily(a.id))) : [];
7508
+ const ranked = [...defaultFamilyMembers.length > 0 ? defaultFamilyMembers : pool].sort(
7509
+ (a, b) => {
7510
+ const majorA = parseInt(a.preferredVersion.split(".")[0], 10) || 0;
7511
+ const majorB = parseInt(b.preferredVersion.split(".")[0], 10) || 0;
7512
+ return majorB - majorA;
7513
+ }
7514
+ );
7515
+ result.push(ranked[0]);
7451
7516
  }
7452
7517
  return result;
7453
7518
  }
@@ -7603,14 +7668,14 @@ var import_tooling6 = require("@codacy/tooling");
7603
7668
  // src/credentials.ts
7604
7669
  var import_crypto = __toESM(require("crypto"));
7605
7670
  var import_promises5 = __toESM(require("fs/promises"));
7606
- var import_os3 = __toESM(require("os"));
7671
+ var import_os4 = __toESM(require("os"));
7607
7672
  var import_path9 = __toESM(require("path"));
7608
7673
  function credentialsPath() {
7609
- return import_path9.default.join(import_os3.default.homedir(), ".codacy", "credentials");
7674
+ return import_path9.default.join(import_os4.default.homedir(), ".codacy", "credentials");
7610
7675
  }
7611
7676
  function deriveKey(salt) {
7612
- const info = import_os3.default.userInfo();
7613
- const machineKey = [import_os3.default.hostname(), info.username, import_os3.default.homedir(), import_os3.default.platform()].join("|");
7677
+ const info = import_os4.default.userInfo();
7678
+ const machineKey = [import_os4.default.hostname(), info.username, import_os4.default.homedir(), import_os4.default.platform()].join("|");
7614
7679
  return import_crypto.default.pbkdf2Sync(machineKey, salt, 1e5, 32, "sha512");
7615
7680
  }
7616
7681
  async function saveCredentials(token) {
@@ -7663,11 +7728,255 @@ async function resolveApiToken() {
7663
7728
  if (stored) return stored;
7664
7729
  throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
7665
7730
  }
7731
+
7732
+ // src/git-info.ts
7733
+ var import_child_process5 = require("child_process");
7734
+ function resolveRepositoryRoot(cwd = process.cwd()) {
7735
+ try {
7736
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7737
+ cwd,
7738
+ encoding: "utf-8"
7739
+ }).trim();
7740
+ } catch {
7741
+ throw new Error(`Not inside a git repository: ${cwd}`);
7742
+ }
7743
+ }
7744
+ function resolveHeadCommit(repositoryRoot) {
7745
+ try {
7746
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
7747
+ cwd: repositoryRoot,
7748
+ encoding: "utf-8"
7749
+ }).trim();
7750
+ } catch {
7751
+ throw new Error(
7752
+ "Could not resolve the current commit from git (no commits yet, or not a git repository). Pass the commit explicitly with --commit <sha>."
7753
+ );
7754
+ }
7755
+ }
7756
+
7757
+ // src/upload.ts
7758
+ var import_promises6 = require("fs/promises");
7759
+ var DEFAULT_RESULTS_API_BASE_URL = "https://api.codacy.com/2.0";
7760
+ function resolveResultsApiBaseUrl() {
7761
+ const raw = process.env.CODACY_RESULTS_API_BASE_URL || DEFAULT_RESULTS_API_BASE_URL;
7762
+ return raw.replace(/\/+$/, "");
7763
+ }
7764
+ function mapSarifLevelToCodacy(level) {
7765
+ switch (level) {
7766
+ case "error":
7767
+ return "Error";
7768
+ case "warning":
7769
+ return "Warning";
7770
+ case "note":
7771
+ return "Info";
7772
+ default:
7773
+ return "Warning";
7774
+ }
7775
+ }
7776
+ function parseReport(fileContents) {
7777
+ let parsed;
7778
+ try {
7779
+ parsed = JSON.parse(fileContents);
7780
+ } catch (err) {
7781
+ throw new Error(
7782
+ `Report is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
7783
+ );
7784
+ }
7785
+ if (!parsed || typeof parsed !== "object") {
7786
+ throw new Error(
7787
+ "Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
7788
+ );
7789
+ }
7790
+ const obj = parsed;
7791
+ const looksSarif = Array.isArray(obj.runs) && (typeof obj.version === "string" && obj.version.startsWith("2.1") || typeof obj.$schema === "string" && obj.$schema.includes("sarif"));
7792
+ if (looksSarif) {
7793
+ return parseSarif(obj.runs);
7794
+ }
7795
+ if (Array.isArray(obj.issues)) {
7796
+ return parseAnalysisResult(obj);
7797
+ }
7798
+ throw new Error(
7799
+ "Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
7800
+ );
7801
+ }
7802
+ function parseSarif(runs) {
7803
+ const issues = [];
7804
+ for (const run of runs) {
7805
+ const toolId = run.tool?.driver?.name;
7806
+ if (!toolId) continue;
7807
+ for (const result of run.results ?? []) {
7808
+ const patternId = result.ruleId;
7809
+ if (!patternId) continue;
7810
+ const level = mapSarifLevelToCodacy(result.level);
7811
+ const message = result.message?.text ?? "";
7812
+ for (const loc of result.locations ?? []) {
7813
+ const filePath = loc.physicalLocation?.artifactLocation?.uri;
7814
+ const line = loc.physicalLocation?.region?.startLine;
7815
+ if (!filePath || !line) continue;
7816
+ issues.push({ toolId, patternId, filePath, line, message, level });
7817
+ }
7818
+ }
7819
+ }
7820
+ return issues;
7821
+ }
7822
+ function parseAnalysisResult(result) {
7823
+ return (result.issues ?? []).map((issue) => ({
7824
+ toolId: issue.toolId,
7825
+ patternId: issue.patternId,
7826
+ filePath: issue.filePath,
7827
+ line: issue.line,
7828
+ message: issue.message,
7829
+ // JSON carries the authoritative Codacy severity; pass it through unchanged.
7830
+ level: issue.severity
7831
+ }));
7832
+ }
7833
+ function buildPayloads(issues, resolveShortName) {
7834
+ const byTool = /* @__PURE__ */ new Map();
7835
+ for (const issue of issues) {
7836
+ const list = byTool.get(issue.toolId);
7837
+ if (list) list.push(issue);
7838
+ else byTool.set(issue.toolId, [issue]);
7839
+ }
7840
+ const payloads = [];
7841
+ for (const [toolId, toolIssues] of byTool) {
7842
+ const byFile = /* @__PURE__ */ new Map();
7843
+ for (const issue of toolIssues) {
7844
+ const entry = {
7845
+ Issue: {
7846
+ patternId: { value: issue.patternId },
7847
+ filename: issue.filePath,
7848
+ message: { text: issue.message },
7849
+ level: issue.level,
7850
+ location: { LineLocation: { line: issue.line } }
7851
+ }
7852
+ };
7853
+ const list = byFile.get(issue.filePath);
7854
+ if (list) list.push(entry);
7855
+ else byFile.set(issue.filePath, [entry]);
7856
+ }
7857
+ const results = [];
7858
+ for (const [filename, fileIssues] of byFile) {
7859
+ results.push({ filename, results: fileIssues });
7860
+ }
7861
+ payloads.push({
7862
+ tool: resolveShortName(toolId),
7863
+ issues: { Success: { results } }
7864
+ });
7865
+ }
7866
+ return payloads;
7867
+ }
7868
+ async function buildShortNameResolver(onProgress) {
7869
+ const byName = /* @__PURE__ */ new Map();
7870
+ try {
7871
+ const tools = await listTools();
7872
+ for (const tool of tools) {
7873
+ if (tool.name && tool.shortName) byName.set(tool.name.toLowerCase(), tool.shortName);
7874
+ }
7875
+ } catch (err) {
7876
+ onProgress?.(
7877
+ `Warning: could not fetch Codacy tools list (${err instanceof Error ? err.message : String(err)}); falling back to tool ids as-is.`
7878
+ );
7879
+ }
7880
+ return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
7881
+ }
7882
+ var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
7883
+ function assertValidCoordinate(kind, value) {
7884
+ if (!COORDINATE_PATTERN.test(value)) {
7885
+ throw new Error(
7886
+ `Invalid ${kind} "${value}": expected only letters, digits, and the characters . _ -`
7887
+ );
7888
+ }
7889
+ }
7890
+ function buildUrls(base, auth, commitUuid) {
7891
+ const commit = encodeURIComponent(commitUuid);
7892
+ if (auth.kind === "repository-token") {
7893
+ return {
7894
+ results: `${base}/commit/${commit}/issuesRemoteResults`,
7895
+ final: `${base}/commit/${commit}/resultsFinal`
7896
+ };
7897
+ }
7898
+ assertValidCoordinate("provider", auth.provider);
7899
+ assertValidCoordinate("owner", auth.owner);
7900
+ assertValidCoordinate("repository", auth.repository);
7901
+ const p = encodeURIComponent(auth.provider);
7902
+ const o = encodeURIComponent(auth.owner);
7903
+ const r = encodeURIComponent(auth.repository);
7904
+ return {
7905
+ results: `${base}/${p}/${o}/${r}/commit/${commit}/issuesRemoteResults`,
7906
+ final: `${base}/${p}/${o}/${r}/commit/${commit}/resultsFinal`
7907
+ };
7908
+ }
7909
+ function authHeader(auth) {
7910
+ return auth.kind === "repository-token" ? { "project-token": auth.token } : { "api-token": auth.token };
7911
+ }
7912
+ async function uploadReport(options) {
7913
+ const { reportPath, commitUuid, auth, onProgress } = options;
7914
+ onProgress?.(`Reading report ${reportPath}...`);
7915
+ const contents = await (0, import_promises6.readFile)(reportPath, "utf-8");
7916
+ const issues = parseReport(contents);
7917
+ if (issues.length === 0) {
7918
+ onProgress?.("No issues found in the report; nothing to upload.");
7919
+ }
7920
+ const resolveShortName = await buildShortNameResolver(onProgress);
7921
+ const payloads = buildPayloads(issues, resolveShortName);
7922
+ const base = resolveResultsApiBaseUrl();
7923
+ const urls = buildUrls(base, auth, commitUuid);
7924
+ const headers = { "content-type": "application/json", ...authHeader(auth) };
7925
+ for (const payload of payloads) {
7926
+ const issuesInTool = payload.issues.Success.results.reduce(
7927
+ (sum, f) => sum + f.results.length,
7928
+ 0
7929
+ );
7930
+ onProgress?.(`Uploading ${issuesInTool} issue(s) for ${payload.tool}...`);
7931
+ const res = await fetch(urls.results, {
7932
+ method: "POST",
7933
+ headers,
7934
+ // The body is a one-element array per tool, matching the Go CLI.
7935
+ body: JSON.stringify([payload])
7936
+ });
7937
+ if (!res.ok) {
7938
+ const body = await safeReadBody(res);
7939
+ throw new Error(
7940
+ `Failed to upload results for ${payload.tool}: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
7941
+ );
7942
+ }
7943
+ }
7944
+ onProgress?.("Finalizing results...");
7945
+ let finalized = false;
7946
+ try {
7947
+ const res = await fetch(urls.final, { method: "POST", headers });
7948
+ finalized = res.ok;
7949
+ if (!res.ok) {
7950
+ const body = await safeReadBody(res);
7951
+ onProgress?.(
7952
+ `Warning: resultsFinal returned ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
7953
+ );
7954
+ }
7955
+ } catch (err) {
7956
+ onProgress?.(
7957
+ `Warning: resultsFinal request failed: ${err instanceof Error ? err.message : String(err)}`
7958
+ );
7959
+ }
7960
+ return {
7961
+ toolsUploaded: payloads.map((p) => p.tool),
7962
+ issueCount: issues.length,
7963
+ finalized
7964
+ };
7965
+ }
7966
+ async function safeReadBody(res) {
7967
+ try {
7968
+ const text = await res.text();
7969
+ return text.length > 500 ? text.slice(0, 500) + "\u2026" : text;
7970
+ } catch {
7971
+ return "";
7972
+ }
7973
+ }
7666
7974
  // Annotate the CommonJS export names for ESM import in node:
7667
7975
  0 && (module.exports = {
7668
7976
  analyze,
7669
7977
  applyCodacyYamlExcludes,
7670
7978
  applyConfigOperation,
7979
+ buildPayloads,
7671
7980
  buildToolConfig,
7672
7981
  clearAdapters,
7673
7982
  computePatternSummary,
@@ -7699,6 +8008,7 @@ async function resolveApiToken() {
7699
8008
  loadCredentials,
7700
8009
  mergeConfigs,
7701
8010
  parseAutoFilters,
8011
+ parseReport,
7702
8012
  pruneEmptyTools,
7703
8013
  readBaselineConfig,
7704
8014
  readCodacyConfig,
@@ -7710,10 +8020,14 @@ async function resolveApiToken() {
7710
8020
  resolveBaselinePath,
7711
8021
  resolveConfigPath,
7712
8022
  resolveEngineToAdapter,
8023
+ resolveHeadCommit,
7713
8024
  resolveProxyConfig,
8025
+ resolveRepositoryRoot,
8026
+ resolveResultsApiBaseUrl,
7714
8027
  rotateLogFiles,
7715
8028
  saveCredentials,
7716
8029
  updateConfigIncremental,
8030
+ uploadReport,
7717
8031
  validateApiToken,
7718
8032
  writeBaselineConfig,
7719
8033
  writeCodacyConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Core orchestration engine for Codacy analysis",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,11 +12,11 @@
12
12
  "cli-table3": "^0.6",
13
13
  "minimatch": "10.2.4",
14
14
  "yaml": "^2.6.0",
15
- "@codacy/tooling": "0.15.0"
15
+ "@codacy/tooling": "0.17.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.15.0"
19
+ "@codacy/tools-dummy-1": "0.17.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",