@codacy/analysis-runner 0.14.0 → 0.16.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
@@ -853,4 +853,174 @@ declare function discover(repositoryRoot: string, options?: DiscoverOptions): Pr
853
853
  */
854
854
  declare function getKnownDisplayNames(): Set<string>;
855
855
 
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 };
856
+ /**
857
+ * Git repository introspection helpers used by commands that need to know
858
+ * *where* they are (repository root) and *what* they are looking at (the current
859
+ * commit SHA). Kept separate from `git-scope.ts` (which resolves file *sets* for
860
+ * `--staged`/`--diff`/`--pr`); this module answers point questions about the repo.
861
+ *
862
+ * Both helpers shell out with `execFileSync` (never a shell string) so paths and
863
+ * refs can't be interpreted by the shell.
864
+ */
865
+ /**
866
+ * Resolves the absolute path to the enclosing git repository's root.
867
+ *
868
+ * Wraps `git rev-parse --show-toplevel`. Throws a clear error when `cwd` is not
869
+ * inside a git working tree, so callers can surface an actionable message rather
870
+ * than a raw git failure.
871
+ */
872
+ declare function resolveRepositoryRoot(cwd?: string): string;
873
+ /**
874
+ * Resolves the SHA of the current `HEAD` commit.
875
+ *
876
+ * Wraps `git rev-parse HEAD`. Used by `upload` to auto-detect the commit when the
877
+ * caller omits `--commit`. Throws when there is no commit yet (empty repo) or the
878
+ * directory is not a git repository, so the command can tell the user to pass
879
+ * `--commit` explicitly.
880
+ */
881
+ declare function resolveHeadCommit(repositoryRoot: string): string;
882
+
883
+ /**
884
+ * `upload` — sends an analysis report produced by this CLI to Codacy, attaching
885
+ * its issues to a commit so they surface on Codacy Cloud.
886
+ *
887
+ * ## What it uploads
888
+ *
889
+ * Only reports this CLI produced, in either output format:
890
+ * - `sarif` — our SARIF v2.1.0 document, where `run.tool.driver.name` is the tool
891
+ * id and each `result.ruleId` is already a Codacy pattern id.
892
+ * - `json` — the full `AnalysisResult`, whose `issues[]` carry `toolId`, `patternId`,
893
+ * `severity`, etc.
894
+ *
895
+ * Because our reports already carry Codacy-native pattern ids, no online pattern
896
+ * lookup is needed (unlike the legacy Go CLI, which had to map raw tool rule ids to
897
+ * Codacy patterns). We normalize either format into a flat issue list and build the
898
+ * payload directly.
899
+ *
900
+ * ## Where it uploads
901
+ *
902
+ * The **legacy v2 API** (`https://api.codacy.com/2.0`), matching the Go CLI: one
903
+ * `issuesRemoteResults` POST per tool, then a single `resultsFinal` POST to close the
904
+ * commit. The backend stores the `issues` payload as an opaque blob and a separate
905
+ * downstream engine parses it, so the payload shape must match the Go CLI's exactly.
906
+ *
907
+ * ## Auth
908
+ *
909
+ * Two mutually exclusive modes (see {@link UploadAuth}): a repository token
910
+ * (`project-token` header, commit-scoped URL) or an account token (`api-token`
911
+ * header, provider/owner/repo-scoped URL).
912
+ */
913
+ /**
914
+ * An issue flattened from either report format into the minimal fields the v2
915
+ * payload needs. `filePath` is relative to the repository root (both our SARIF and
916
+ * JSON emit repo-relative paths, so no recomputation is required).
917
+ */
918
+ interface NormalizedIssue {
919
+ /** Tool id (our adapter id, e.g. "ESLint9"); becomes the payload's tool grouping. */
920
+ toolId: string;
921
+ /** Codacy pattern id (e.g. "ESLint9_accessor-pairs"). */
922
+ patternId: string;
923
+ /** Repository-relative file path. */
924
+ filePath: string;
925
+ /** 1-based line number. */
926
+ line: number;
927
+ /** Issue message text. */
928
+ message: string;
929
+ /** Codacy level string (Info/Warning/High/Error for json; Info/Warning/Error for sarif). */
930
+ level: string;
931
+ }
932
+ /** Authentication + routing for the upload. */
933
+ type UploadAuth = {
934
+ kind: "repository-token";
935
+ /** Repository (a.k.a. project) token; sent as the `project-token` header. */
936
+ token: string;
937
+ } | {
938
+ kind: "account-token";
939
+ /** Account/user token; sent as the `api-token` header. */
940
+ token: string;
941
+ provider: string;
942
+ owner: string;
943
+ repository: string;
944
+ };
945
+ /** Options for {@link uploadReport}. */
946
+ interface UploadOptions {
947
+ /** Absolute or CWD-relative path to the report file (sarif or json). */
948
+ reportPath: string;
949
+ /** Commit SHA/UUID the results attach to. */
950
+ commitUuid: string;
951
+ auth: UploadAuth;
952
+ /** Progress callback for human-facing messages (stderr in the CLI). */
953
+ onProgress?: (message: string) => void;
954
+ }
955
+ /** Outcome of an upload. */
956
+ interface UploadResult {
957
+ /** Tool short-names that had at least one issue uploaded. */
958
+ toolsUploaded: string[];
959
+ /** Total issues uploaded across all tools. */
960
+ issueCount: number;
961
+ /** Whether the final `resultsFinal` call succeeded. */
962
+ finalized: boolean;
963
+ }
964
+ /** The per-tool payload object (wrapped in a one-element array per request). */
965
+ interface ToolPayload {
966
+ tool: string;
967
+ issues: {
968
+ Success: {
969
+ results: FileResults[];
970
+ };
971
+ };
972
+ }
973
+ interface FileResults {
974
+ filename: string;
975
+ results: Array<{
976
+ Issue: PayloadIssue;
977
+ }>;
978
+ }
979
+ interface PayloadIssue {
980
+ patternId: {
981
+ value: string;
982
+ };
983
+ filename: string;
984
+ message: {
985
+ text: string;
986
+ };
987
+ level: string;
988
+ location: {
989
+ LineLocation: {
990
+ line: number;
991
+ };
992
+ };
993
+ }
994
+ /**
995
+ * Resolves the base URL for the legacy v2 results API. Defaults to Codacy SaaS;
996
+ * override with `CODACY_RESULTS_API_BASE_URL` for self-hosted deployments. Note this
997
+ * is a different host from `CODACY_API_BASE_URL` (the v3 app API).
998
+ */
999
+ declare function resolveResultsApiBaseUrl(): string;
1000
+ /**
1001
+ * Detects the report format and flattens it into {@link NormalizedIssue}s.
1002
+ * Accepts only reports produced by this CLI (our SARIF or our JSON `AnalysisResult`);
1003
+ * throws a clear error otherwise.
1004
+ */
1005
+ declare function parseReport(fileContents: string): NormalizedIssue[];
1006
+ /**
1007
+ * Builds one v2 payload per tool, grouping issues by file. `resolveShortName` maps
1008
+ * our internal tool id to the Codacy tool short-name expected in the `tool` field
1009
+ * (which becomes an S3 key segment consumed downstream).
1010
+ */
1011
+ declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string): ToolPayload[];
1012
+ /**
1013
+ * Uploads an analysis report to Codacy. Reads and parses the report, resolves tool
1014
+ * short-names, POSTs one `issuesRemoteResults` per tool, then a single `resultsFinal`.
1015
+ *
1016
+ * Throws on a failed `issuesRemoteResults` POST (partial uploads shouldn't be
1017
+ * silently finalized). A failed `resultsFinal` is surfaced via `onProgress` and
1018
+ * reflected in the returned {@link UploadResult}, matching the Go CLI's non-fatal
1019
+ * treatment of finalization.
1020
+ *
1021
+ * Proxy/TLS is honored automatically — all `fetch` traffic goes through the global
1022
+ * dispatcher installed by `configureProxy()`.
1023
+ */
1024
+ declare function uploadReport(options: UploadOptions): Promise<UploadResult>;
1025
+
1026
+ 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,
@@ -89,6 +95,7 @@ module.exports = __toCommonJS(index_exports);
89
95
  // src/analyze.ts
90
96
  var import_os2 = __toESM(require("os"));
91
97
  var import_path4 = __toESM(require("path"));
98
+ var import_fs3 = __toESM(require("fs"));
92
99
  var import_child_process3 = require("child_process");
93
100
  var import_util = require("util");
94
101
  var import_tooling3 = require("@codacy/tooling");
@@ -586,7 +593,7 @@ function routeFilesToTool(files, adapter, toolConfig) {
586
593
  }
587
594
  function filterByTargetFiles(allFiles, targetPatterns) {
588
595
  if (targetPatterns.length === 0) return allFiles;
589
- const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
596
+ const normalized = targetPatterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
590
597
  const exact = new Set(normalized);
591
598
  const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
592
599
  const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
@@ -1053,6 +1060,26 @@ async function analyze(options) {
1053
1060
  []
1054
1061
  );
1055
1062
  }
1063
+ const rootStat = import_fs3.default.statSync(options.repositoryRoot, { throwIfNoEntry: false });
1064
+ if (!rootStat?.isDirectory()) {
1065
+ return buildResult(
1066
+ startedAt,
1067
+ options.repositoryRoot,
1068
+ mode,
1069
+ emptyCapability,
1070
+ [],
1071
+ [
1072
+ {
1073
+ toolId: "runner",
1074
+ phase: "requirementCheck",
1075
+ kind: "RepositoryNotFound",
1076
+ message: `${options.repositoryRoot} does not exist or is not a directory. Pass a path to an existing repository, or a quoted glob (e.g. "src/**/*.ts") to scope files.`,
1077
+ level: "error"
1078
+ }
1079
+ ],
1080
+ []
1081
+ );
1082
+ }
1056
1083
  try {
1057
1084
  await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
1058
1085
  cwd: options.repositoryRoot
@@ -2604,11 +2631,24 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2604
2631
  // --- Scala — Frameworks ---
2605
2632
  ["play-server", { displayName: "Play Framework", kind: "framework" }],
2606
2633
  ["akka-actor", { displayName: "Akka", kind: "framework" }],
2634
+ ["spray-can", { displayName: "Spray", kind: "framework" }],
2607
2635
  // --- Scala — Libraries ---
2608
2636
  ["scalatest", { displayName: "ScalaTest", kind: "library" }],
2609
2637
  ["specs2-core", { displayName: "Specs2", kind: "library" }],
2610
2638
  ["cats-core", { displayName: "Cats", kind: "library" }],
2611
2639
  ["circe-core", { displayName: "Circe", kind: "library" }],
2640
+ // --- Elixir — Frameworks ---
2641
+ ["phoenix", { displayName: "Phoenix", kind: "framework" }],
2642
+ ["plug", { displayName: "Plug", kind: "framework" }],
2643
+ ["plug_cowboy", { displayName: "Plug", kind: "framework" }],
2644
+ // --- Elixir — Libraries ---
2645
+ ["ecto", { displayName: "Ecto", kind: "library" }],
2646
+ ["ecto_sql", { displayName: "Ecto", kind: "library" }],
2647
+ ["phoenix_ecto", { displayName: "Ecto", kind: "library" }],
2648
+ // --- Crystal — Frameworks ---
2649
+ ["amber", { displayName: "Amber", kind: "framework" }],
2650
+ ["lucky", { displayName: "Lucky", kind: "framework" }],
2651
+ ["kemal", { displayName: "Kemal", kind: "framework" }],
2612
2652
  // --- Dart — Frameworks ---
2613
2653
  // `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
2614
2654
  // here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
@@ -2746,6 +2786,31 @@ var FILE_SIGNAL_RULES = [
2746
2786
  name: "suitecloud.config.js",
2747
2787
  matches: (f) => pathBasename(f) === "suitecloud.config.js",
2748
2788
  classification: { displayName: "SuiteScript", kind: "platform" }
2789
+ },
2790
+ {
2791
+ name: ".github/workflows",
2792
+ matches: (f) => f.startsWith(".github/workflows/"),
2793
+ classification: { displayName: "GitHub Actions", kind: "framework" }
2794
+ },
2795
+ {
2796
+ name: ".circleci",
2797
+ matches: (f) => f.startsWith(".circleci/"),
2798
+ classification: { displayName: "CircleCI", kind: "framework" }
2799
+ },
2800
+ {
2801
+ name: ".gitlab-ci.yml",
2802
+ matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
2803
+ classification: { displayName: "GitLab CI", kind: "framework" }
2804
+ },
2805
+ {
2806
+ name: "bitbucket-pipelines.yml",
2807
+ matches: (f) => f === "bitbucket-pipelines.yml",
2808
+ classification: { displayName: "Bitbucket Pipelines", kind: "framework" }
2809
+ },
2810
+ {
2811
+ name: "azure-pipelines.yml",
2812
+ matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
2813
+ classification: { displayName: "Azure Pipelines", kind: "framework" }
2749
2814
  }
2750
2815
  ];
2751
2816
  function detectFileSignals(files) {
@@ -2991,6 +3056,15 @@ function parseGemfile(content) {
2991
3056
  }
2992
3057
  return deps;
2993
3058
  }
3059
+ function parseMixExs(content) {
3060
+ const deps = [];
3061
+ const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
3062
+ let match;
3063
+ while ((match = depRegex.exec(content)) !== null) {
3064
+ deps.push({ name: match[1], version: match[2] || void 0 });
3065
+ }
3066
+ return deps;
3067
+ }
2994
3068
  function parseGoMod(content) {
2995
3069
  const deps = [];
2996
3070
  const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
@@ -3030,6 +3104,15 @@ function parseBuildGradle(content) {
3030
3104
  }
3031
3105
  return deps;
3032
3106
  }
3107
+ function parseBuildSbt(content) {
3108
+ const deps = [];
3109
+ const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%\s*"([^"]+)"/g;
3110
+ let match;
3111
+ while ((match = depRegex.exec(content)) !== null) {
3112
+ deps.push({ name: match[2], version: match[3] });
3113
+ }
3114
+ return deps;
3115
+ }
3033
3116
  function parseCargoToml(content) {
3034
3117
  const deps = [];
3035
3118
  let inDepsSection = false;
@@ -3110,6 +3193,41 @@ function parsePubspecYaml(content) {
3110
3193
  }
3111
3194
  return deps;
3112
3195
  }
3196
+ function parseShardYml(content) {
3197
+ const deps = [];
3198
+ let inSection = false;
3199
+ let depIndent = -1;
3200
+ let current;
3201
+ const flush = () => {
3202
+ if (current) deps.push(current);
3203
+ current = void 0;
3204
+ };
3205
+ for (const rawLine of content.split("\n")) {
3206
+ const trimmed = rawLine.trim();
3207
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
3208
+ const indent = rawLine.length - rawLine.trimStart().length;
3209
+ if (indent === 0) {
3210
+ flush();
3211
+ inSection = /^(dependencies|development_dependencies):/.test(trimmed);
3212
+ depIndent = -1;
3213
+ continue;
3214
+ }
3215
+ if (!inSection) continue;
3216
+ if (depIndent === -1) depIndent = indent;
3217
+ if (indent === depIndent) {
3218
+ flush();
3219
+ const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
3220
+ current = m ? { name: m[1] } : void 0;
3221
+ continue;
3222
+ }
3223
+ if (indent > depIndent && current) {
3224
+ const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
3225
+ if (v) current.version = v[1];
3226
+ }
3227
+ }
3228
+ flush();
3229
+ return deps;
3230
+ }
3113
3231
  function parseXmlDependencyElements(content, elementRegex, nameAttr) {
3114
3232
  const deps = [];
3115
3233
  const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
@@ -3144,11 +3262,14 @@ var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
3144
3262
  ["pom.xml", parsePomXml],
3145
3263
  ["build.gradle", parseBuildGradle],
3146
3264
  ["build.gradle.kts", parseBuildGradle],
3265
+ ["build.sbt", parseBuildSbt],
3147
3266
  ["Cargo.toml", parseCargoToml],
3148
3267
  ["composer.json", parseComposerJson],
3149
3268
  ["Podfile", parsePodfile],
3150
3269
  ["Package.swift", parsePackageSwift],
3151
3270
  ["pubspec.yaml", parsePubspecYaml],
3271
+ ["mix.exs", parseMixExs],
3272
+ ["shard.yml", parseShardYml],
3152
3273
  ["packages.config", parsePackagesConfig],
3153
3274
  ["Directory.Packages.props", parseCsproj]
3154
3275
  ]);
@@ -7197,6 +7318,11 @@ async function initContainerConfig(repositoryRoot, adapters) {
7197
7318
  }
7198
7319
  return createCodacyConfig({ source: "container", tools, includeFiles });
7199
7320
  }
7321
+ function hasMatchingFiles(files, filePatterns) {
7322
+ const matcher = new CompiledMatcher(filePatterns);
7323
+ if (matcher.matchAll) return files.length > 0;
7324
+ return files.some((file) => matcher.matches(file, file.split("/").pop() ?? ""));
7325
+ }
7200
7326
  async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
7201
7327
  const discoveryResult = await discover(repositoryRoot);
7202
7328
  onDiscoveryComplete?.(discoveryResult);
@@ -7206,6 +7332,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7206
7332
  for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
7207
7333
  const repoLanguages = new Set(discoveryResult.languages);
7208
7334
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
7335
+ const repoFiles = listRepoFiles(repositoryRoot);
7209
7336
  const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
7210
7337
  const candidates = [];
7211
7338
  for (const adapter of adapters) {
@@ -7213,6 +7340,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7213
7340
  const hasOverlap = adapter.languages.some((lang) => repoLanguages.has(lang));
7214
7341
  if (!hasOverlap) continue;
7215
7342
  }
7343
+ if (!hasMatchingFiles(repoFiles, adapter.filePatterns)) continue;
7216
7344
  candidates.push(adapter);
7217
7345
  }
7218
7346
  const deduped = deduplicateToolFamilies(candidates, localConfigs);
@@ -7256,6 +7384,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7256
7384
  }
7257
7385
  const family = toolFamily(desc.id);
7258
7386
  if (selectedFamilies.has(family)) continue;
7387
+ if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
7259
7388
  const patternsMetadata = loadUnsupportedPatterns(desc.id);
7260
7389
  if (patternsMetadata.length === 0) continue;
7261
7390
  const selectedPatterns = filterAutoPatterns(
@@ -7540,11 +7669,255 @@ async function resolveApiToken() {
7540
7669
  if (stored) return stored;
7541
7670
  throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
7542
7671
  }
7672
+
7673
+ // src/git-info.ts
7674
+ var import_child_process5 = require("child_process");
7675
+ function resolveRepositoryRoot(cwd = process.cwd()) {
7676
+ try {
7677
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7678
+ cwd,
7679
+ encoding: "utf-8"
7680
+ }).trim();
7681
+ } catch {
7682
+ throw new Error(`Not inside a git repository: ${cwd}`);
7683
+ }
7684
+ }
7685
+ function resolveHeadCommit(repositoryRoot) {
7686
+ try {
7687
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
7688
+ cwd: repositoryRoot,
7689
+ encoding: "utf-8"
7690
+ }).trim();
7691
+ } catch {
7692
+ throw new Error(
7693
+ "Could not resolve the current commit from git (no commits yet, or not a git repository). Pass the commit explicitly with --commit <sha>."
7694
+ );
7695
+ }
7696
+ }
7697
+
7698
+ // src/upload.ts
7699
+ var import_promises6 = require("fs/promises");
7700
+ var DEFAULT_RESULTS_API_BASE_URL = "https://api.codacy.com/2.0";
7701
+ function resolveResultsApiBaseUrl() {
7702
+ const raw = process.env.CODACY_RESULTS_API_BASE_URL || DEFAULT_RESULTS_API_BASE_URL;
7703
+ return raw.replace(/\/+$/, "");
7704
+ }
7705
+ function mapSarifLevelToCodacy(level) {
7706
+ switch (level) {
7707
+ case "error":
7708
+ return "Error";
7709
+ case "warning":
7710
+ return "Warning";
7711
+ case "note":
7712
+ return "Info";
7713
+ default:
7714
+ return "Warning";
7715
+ }
7716
+ }
7717
+ function parseReport(fileContents) {
7718
+ let parsed;
7719
+ try {
7720
+ parsed = JSON.parse(fileContents);
7721
+ } catch (err) {
7722
+ throw new Error(
7723
+ `Report is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
7724
+ );
7725
+ }
7726
+ if (!parsed || typeof parsed !== "object") {
7727
+ throw new Error(
7728
+ "Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
7729
+ );
7730
+ }
7731
+ const obj = parsed;
7732
+ const looksSarif = Array.isArray(obj.runs) && (typeof obj.version === "string" && obj.version.startsWith("2.1") || typeof obj.$schema === "string" && obj.$schema.includes("sarif"));
7733
+ if (looksSarif) {
7734
+ return parseSarif(obj.runs);
7735
+ }
7736
+ if (Array.isArray(obj.issues)) {
7737
+ return parseAnalysisResult(obj);
7738
+ }
7739
+ throw new Error(
7740
+ "Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
7741
+ );
7742
+ }
7743
+ function parseSarif(runs) {
7744
+ const issues = [];
7745
+ for (const run of runs) {
7746
+ const toolId = run.tool?.driver?.name;
7747
+ if (!toolId) continue;
7748
+ for (const result of run.results ?? []) {
7749
+ const patternId = result.ruleId;
7750
+ if (!patternId) continue;
7751
+ const level = mapSarifLevelToCodacy(result.level);
7752
+ const message = result.message?.text ?? "";
7753
+ for (const loc of result.locations ?? []) {
7754
+ const filePath = loc.physicalLocation?.artifactLocation?.uri;
7755
+ const line = loc.physicalLocation?.region?.startLine;
7756
+ if (!filePath || !line) continue;
7757
+ issues.push({ toolId, patternId, filePath, line, message, level });
7758
+ }
7759
+ }
7760
+ }
7761
+ return issues;
7762
+ }
7763
+ function parseAnalysisResult(result) {
7764
+ return (result.issues ?? []).map((issue) => ({
7765
+ toolId: issue.toolId,
7766
+ patternId: issue.patternId,
7767
+ filePath: issue.filePath,
7768
+ line: issue.line,
7769
+ message: issue.message,
7770
+ // JSON carries the authoritative Codacy severity; pass it through unchanged.
7771
+ level: issue.severity
7772
+ }));
7773
+ }
7774
+ function buildPayloads(issues, resolveShortName) {
7775
+ const byTool = /* @__PURE__ */ new Map();
7776
+ for (const issue of issues) {
7777
+ const list = byTool.get(issue.toolId);
7778
+ if (list) list.push(issue);
7779
+ else byTool.set(issue.toolId, [issue]);
7780
+ }
7781
+ const payloads = [];
7782
+ for (const [toolId, toolIssues] of byTool) {
7783
+ const byFile = /* @__PURE__ */ new Map();
7784
+ for (const issue of toolIssues) {
7785
+ const entry = {
7786
+ Issue: {
7787
+ patternId: { value: issue.patternId },
7788
+ filename: issue.filePath,
7789
+ message: { text: issue.message },
7790
+ level: issue.level,
7791
+ location: { LineLocation: { line: issue.line } }
7792
+ }
7793
+ };
7794
+ const list = byFile.get(issue.filePath);
7795
+ if (list) list.push(entry);
7796
+ else byFile.set(issue.filePath, [entry]);
7797
+ }
7798
+ const results = [];
7799
+ for (const [filename, fileIssues] of byFile) {
7800
+ results.push({ filename, results: fileIssues });
7801
+ }
7802
+ payloads.push({
7803
+ tool: resolveShortName(toolId),
7804
+ issues: { Success: { results } }
7805
+ });
7806
+ }
7807
+ return payloads;
7808
+ }
7809
+ async function buildShortNameResolver(onProgress) {
7810
+ const byName = /* @__PURE__ */ new Map();
7811
+ try {
7812
+ const tools = await listTools();
7813
+ for (const tool of tools) {
7814
+ if (tool.name && tool.shortName) byName.set(tool.name.toLowerCase(), tool.shortName);
7815
+ }
7816
+ } catch (err) {
7817
+ onProgress?.(
7818
+ `Warning: could not fetch Codacy tools list (${err instanceof Error ? err.message : String(err)}); falling back to tool ids as-is.`
7819
+ );
7820
+ }
7821
+ return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
7822
+ }
7823
+ var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
7824
+ function assertValidCoordinate(kind, value) {
7825
+ if (!COORDINATE_PATTERN.test(value)) {
7826
+ throw new Error(
7827
+ `Invalid ${kind} "${value}": expected only letters, digits, and the characters . _ -`
7828
+ );
7829
+ }
7830
+ }
7831
+ function buildUrls(base, auth, commitUuid) {
7832
+ const commit = encodeURIComponent(commitUuid);
7833
+ if (auth.kind === "repository-token") {
7834
+ return {
7835
+ results: `${base}/commit/${commit}/issuesRemoteResults`,
7836
+ final: `${base}/commit/${commit}/resultsFinal`
7837
+ };
7838
+ }
7839
+ assertValidCoordinate("provider", auth.provider);
7840
+ assertValidCoordinate("owner", auth.owner);
7841
+ assertValidCoordinate("repository", auth.repository);
7842
+ const p = encodeURIComponent(auth.provider);
7843
+ const o = encodeURIComponent(auth.owner);
7844
+ const r = encodeURIComponent(auth.repository);
7845
+ return {
7846
+ results: `${base}/${p}/${o}/${r}/commit/${commit}/issuesRemoteResults`,
7847
+ final: `${base}/${p}/${o}/${r}/commit/${commit}/resultsFinal`
7848
+ };
7849
+ }
7850
+ function authHeader(auth) {
7851
+ return auth.kind === "repository-token" ? { "project-token": auth.token } : { "api-token": auth.token };
7852
+ }
7853
+ async function uploadReport(options) {
7854
+ const { reportPath, commitUuid, auth, onProgress } = options;
7855
+ onProgress?.(`Reading report ${reportPath}...`);
7856
+ const contents = await (0, import_promises6.readFile)(reportPath, "utf-8");
7857
+ const issues = parseReport(contents);
7858
+ if (issues.length === 0) {
7859
+ onProgress?.("No issues found in the report; nothing to upload.");
7860
+ }
7861
+ const resolveShortName = await buildShortNameResolver(onProgress);
7862
+ const payloads = buildPayloads(issues, resolveShortName);
7863
+ const base = resolveResultsApiBaseUrl();
7864
+ const urls = buildUrls(base, auth, commitUuid);
7865
+ const headers = { "content-type": "application/json", ...authHeader(auth) };
7866
+ for (const payload of payloads) {
7867
+ const issuesInTool = payload.issues.Success.results.reduce(
7868
+ (sum, f) => sum + f.results.length,
7869
+ 0
7870
+ );
7871
+ onProgress?.(`Uploading ${issuesInTool} issue(s) for ${payload.tool}...`);
7872
+ const res = await fetch(urls.results, {
7873
+ method: "POST",
7874
+ headers,
7875
+ // The body is a one-element array per tool, matching the Go CLI.
7876
+ body: JSON.stringify([payload])
7877
+ });
7878
+ if (!res.ok) {
7879
+ const body = await safeReadBody(res);
7880
+ throw new Error(
7881
+ `Failed to upload results for ${payload.tool}: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
7882
+ );
7883
+ }
7884
+ }
7885
+ onProgress?.("Finalizing results...");
7886
+ let finalized = false;
7887
+ try {
7888
+ const res = await fetch(urls.final, { method: "POST", headers });
7889
+ finalized = res.ok;
7890
+ if (!res.ok) {
7891
+ const body = await safeReadBody(res);
7892
+ onProgress?.(
7893
+ `Warning: resultsFinal returned ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
7894
+ );
7895
+ }
7896
+ } catch (err) {
7897
+ onProgress?.(
7898
+ `Warning: resultsFinal request failed: ${err instanceof Error ? err.message : String(err)}`
7899
+ );
7900
+ }
7901
+ return {
7902
+ toolsUploaded: payloads.map((p) => p.tool),
7903
+ issueCount: issues.length,
7904
+ finalized
7905
+ };
7906
+ }
7907
+ async function safeReadBody(res) {
7908
+ try {
7909
+ const text = await res.text();
7910
+ return text.length > 500 ? text.slice(0, 500) + "\u2026" : text;
7911
+ } catch {
7912
+ return "";
7913
+ }
7914
+ }
7543
7915
  // Annotate the CommonJS export names for ESM import in node:
7544
7916
  0 && (module.exports = {
7545
7917
  analyze,
7546
7918
  applyCodacyYamlExcludes,
7547
7919
  applyConfigOperation,
7920
+ buildPayloads,
7548
7921
  buildToolConfig,
7549
7922
  clearAdapters,
7550
7923
  computePatternSummary,
@@ -7576,6 +7949,7 @@ async function resolveApiToken() {
7576
7949
  loadCredentials,
7577
7950
  mergeConfigs,
7578
7951
  parseAutoFilters,
7952
+ parseReport,
7579
7953
  pruneEmptyTools,
7580
7954
  readBaselineConfig,
7581
7955
  readCodacyConfig,
@@ -7587,10 +7961,14 @@ async function resolveApiToken() {
7587
7961
  resolveBaselinePath,
7588
7962
  resolveConfigPath,
7589
7963
  resolveEngineToAdapter,
7964
+ resolveHeadCommit,
7590
7965
  resolveProxyConfig,
7966
+ resolveRepositoryRoot,
7967
+ resolveResultsApiBaseUrl,
7591
7968
  rotateLogFiles,
7592
7969
  saveCredentials,
7593
7970
  updateConfigIncremental,
7971
+ uploadReport,
7594
7972
  validateApiToken,
7595
7973
  writeBaselineConfig,
7596
7974
  writeCodacyConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.14.0",
3
+ "version": "0.16.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.14.0"
15
+ "@codacy/tooling": "0.16.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.14.0"
19
+ "@codacy/tools-dummy-1": "0.16.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",