@codacy/analysis-runner 0.13.0 → 0.15.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
@@ -1,5 +1,5 @@
1
- import { OutputFormat, Logger, AnalysisResult, ToolAdapter, AdapterDescriptor, CodacyConfig, ConfigurationFileCheckResult, CodacyToolConfig, IssueSeverity, IssueCategory, Language, DiscoveryResult, PatternEntry, Issue, PatternSummary, LogLevel } from '@codacy/tooling';
2
- export { AnalysisResult, OutputFormat } from '@codacy/tooling';
1
+ import { OutputFormat, Logger, ProxyConfig, AnalysisResult, ToolAdapter, AdapterDescriptor, CodacyConfig, ConfigurationFileCheckResult, CodacyToolConfig, IssueSeverity, IssueCategory, Language, DiscoveryResult, PatternEntry, Issue, PatternSummary, LogLevel } from '@codacy/tooling';
2
+ export { AnalysisResult, OutputFormat, ProxyConfig, configureProxy, resolveProxyConfig } from '@codacy/tooling';
3
3
 
4
4
  /**
5
5
  * Main analysis entry point — orchestrates the full analysis pipeline.
@@ -66,6 +66,15 @@ interface AnalyzeOptions {
66
66
  onToolComplete?: (toolId: string, displayName: string, issueCount: number, durationMs: number) => void;
67
67
  /** Called when a tool reports mid-execution progress (e.g. "Linting 50/200 files"). */
68
68
  onToolProgress?: (toolId: string, displayName: string, message: string) => void;
69
+ /**
70
+ * Optional proxy/TLS configuration for outbound HTTP (API calls and tool
71
+ * downloads). When provided, it is applied via `configureProxy` at the start of
72
+ * `analyze()`, installing a global fetch dispatcher. Library consumers that
73
+ * make API calls outside `analyze()` (e.g. init) should instead call
74
+ * `configureProxy` themselves once at startup. Omit to rely on environment
75
+ * variables (HTTP(S)_PROXY, NO_PROXY, SSL_CERT_FILE, CODACY_CLI_INSECURE).
76
+ */
77
+ proxy?: Partial<ProxyConfig>;
69
78
  }
70
79
  /**
71
80
  * Runs analysis on the given repository and returns the unified result.
@@ -181,6 +190,15 @@ declare function formatTextSummary(result: AnalysisResult, isInspect?: boolean):
181
190
  * (e.g. synced from Codacy Cloud) while testing a variant side-by-side.
182
191
  */
183
192
  declare function resolveConfigPath(repositoryRoot: string, configFile?: string): string;
193
+ /**
194
+ * Resolves the absolute path to the config's baseline snapshot — the config the
195
+ * generator last produced, used by `update-config` to distinguish user-disabled
196
+ * patterns from default-off ones. It is a sibling of the config file
197
+ * (`<name>.json` → `<name>.baseline.json`), so `.codacy/codacy.config.json` maps
198
+ * to `.codacy/codacy.config.baseline.json`. It lives outside `.codacy/generated/`
199
+ * so it is committed and travels with the config.
200
+ */
201
+ declare function resolveBaselinePath(repositoryRoot: string, configFile?: string): string;
184
202
  /**
185
203
  * Reads a CodacyConfig from disk.
186
204
  * Returns null if the file doesn't exist. Throws on parse/permission errors
@@ -189,6 +207,19 @@ declare function resolveConfigPath(repositoryRoot: string, configFile?: string):
189
207
  * Pass `configFile` to read from a non-default location.
190
208
  */
191
209
  declare function readCodacyConfig(repositoryRoot: string, configFile?: string): Promise<CodacyConfig | null>;
210
+ /**
211
+ * Reads the config's baseline snapshot (see {@link resolveBaselinePath}).
212
+ * Returns null when no baseline exists (e.g. a config created before baselines
213
+ * were introduced), so `update-config` can fall back gracefully.
214
+ */
215
+ declare function readBaselineConfig(repositoryRoot: string, configFile?: string): Promise<CodacyConfig | null>;
216
+ /**
217
+ * Writes the config's baseline snapshot next to the config file (see
218
+ * {@link resolveBaselinePath}). No `.codacy/` side effects. Callers write this
219
+ * after every `init`/`update-config` so the next update diffs against the exact
220
+ * generator output. Returns the path written.
221
+ */
222
+ declare function writeBaselineConfig(repositoryRoot: string, config: CodacyConfig, configFile?: string): Promise<string>;
192
223
  /**
193
224
  * Writes a CodacyConfig to disk at the exact resolved path, creating only the
194
225
  * parent directory. Unlike {@link writeCodacyConfig}, this has NO `.codacy/`
@@ -261,13 +292,23 @@ declare function createCodacyConfig(params?: {
261
292
 
262
293
  /** The set operation to apply between two configs. */
263
294
  type ConfigOperation = "merge" | "intersect" | "diff";
295
+ /** Options for {@link mergeConfigs}. */
296
+ interface MergeOptions {
297
+ /**
298
+ * When a pattern exists on both sides, keep dest's parameters instead of the
299
+ * default source-wins behavior. Used by the incremental update so the user's
300
+ * tuned parameters (dest) survive over freshly-generated defaults (source).
301
+ */
302
+ preferDestParameters?: boolean;
303
+ }
264
304
  /**
265
305
  * Union of source and dest at the tool and pattern level.
266
306
  * Tools present on only one side are carried over as-is; tools on both sides are
267
- * combined (config-file rule, then per-tool pattern union with source parameters
268
- * winning). Never drops tools.
307
+ * combined (config-file rule, then per-tool pattern union). Parameters of a
308
+ * pattern on both sides come from source unless `options.preferDestParameters`.
309
+ * Never drops tools.
269
310
  */
270
- declare function mergeConfigs(source: CodacyConfig, dest: CodacyConfig): CodacyConfig;
311
+ declare function mergeConfigs(source: CodacyConfig, dest: CodacyConfig, options?: MergeOptions): CodacyConfig;
271
312
  /**
272
313
  * Intersection of source and dest: only tools present in both, and per-tool only
273
314
  * the patterns present in both (config-file rule applies when either side uses a
@@ -286,6 +327,40 @@ declare function intersectConfigs(source: CodacyConfig, dest: CodacyConfig): Cod
286
327
  declare function diffConfigs(source: CodacyConfig, dest: CodacyConfig): CodacyConfig;
287
328
  /** Dispatches to the {@link mergeConfigs}, {@link intersectConfigs}, or {@link diffConfigs}. */
288
329
  declare function applyConfigOperation(op: ConfigOperation, source: CodacyConfig, dest: CodacyConfig): CodacyConfig;
330
+ /**
331
+ * Returns a copy of `config` with tools that would do nothing removed — a tool
332
+ * with no enabled patterns and no native config file. Used after the incremental
333
+ * update so a tool whose every pattern was removed (e.g. its language/framework
334
+ * left the repo) drops out entirely. Config-file tools are always kept (they run
335
+ * from their own file). Does not mutate the input.
336
+ */
337
+ declare function pruneEmptyTools(config: CodacyConfig): CodacyConfig;
338
+ /**
339
+ * Incrementally updates a Codacy config while preserving user customizations.
340
+ *
341
+ * Three-way merge over the enabled-only config model (a disabled pattern is
342
+ * indistinguishable from a default-off one, so a naive re-generate + merge would
343
+ * resurrect patterns the user deliberately removed):
344
+ *
345
+ * - `base` — the config the generator last produced (pre-user-edit baseline)
346
+ * - `current` — the user's live config (baseline + their edits)
347
+ * - `next` — the config the generator produces now (for the current stack)
348
+ *
349
+ * added = next − base (genuinely new tools/patterns to add)
350
+ * removed = base − next (tools/patterns whose stack element is gone)
351
+ * result = (current − removed) + added
352
+ *
353
+ * User-disabled patterns stay absent (they're in `base`, so never in `added`);
354
+ * user-tuned parameters and manual excludes ride along in `current` (dest wins on
355
+ * parameter conflicts via `preferDestParameters`). Identity/provenance metadata
356
+ * (`createdAt`, `source`, organization, …) come from `current`; `updatedAt` is
357
+ * refreshed and `metadata.languages` is refreshed from `next` (an incremental
358
+ * update is effectively a re-discovery, so the recorded stack must reflect the
359
+ * current repository, not the languages captured when the config was first
360
+ * written). Top-level `exclude` is reconciled with the same three-way logic, and
361
+ * any tool left with no patterns and no config file is pruned. Inputs are not mutated.
362
+ */
363
+ declare function updateConfigIncremental(base: CodacyConfig, current: CodacyConfig, next: CodacyConfig): CodacyConfig;
289
364
 
290
365
  /**
291
366
  * auto-filters.ts — Parses and applies --auto filter syntax.
@@ -778,4 +853,4 @@ declare function discover(repositoryRoot: string, options?: DiscoverOptions): Pr
778
853
  */
779
854
  declare function getKnownDisplayNames(): Set<string>;
780
855
 
781
- 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 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, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveConfigPath, resolveEngineToAdapter, rotateLogFiles, saveCredentials, validateApiToken, writeCodacyConfig, writeCodacyConfigFile };
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 };
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  clearAdapters: () => clearAdapters,
38
38
  computePatternSummary: () => computePatternSummary,
39
39
  configureApiToken: () => configureApiToken,
40
+ configureProxy: () => import_tooling6.configureProxy,
40
41
  createCodacyConfig: () => createCodacyConfig,
41
42
  createLogger: () => createLogger,
42
43
  createNullLogger: () => createNullLogger,
@@ -63,17 +64,23 @@ __export(index_exports, {
63
64
  loadCredentials: () => loadCredentials,
64
65
  mergeConfigs: () => mergeConfigs,
65
66
  parseAutoFilters: () => parseAutoFilters,
67
+ pruneEmptyTools: () => pruneEmptyTools,
68
+ readBaselineConfig: () => readBaselineConfig,
66
69
  readCodacyConfig: () => readCodacyConfig,
67
70
  readCodacyYaml: () => readCodacyYaml,
68
71
  registerAdapter: () => registerAdapter,
69
72
  registerLazyAdapter: () => registerLazyAdapter,
70
73
  registerUnsupportedTool: () => registerUnsupportedTool,
71
74
  resolveApiToken: () => resolveApiToken,
75
+ resolveBaselinePath: () => resolveBaselinePath,
72
76
  resolveConfigPath: () => resolveConfigPath,
73
77
  resolveEngineToAdapter: () => resolveEngineToAdapter,
78
+ resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
74
79
  rotateLogFiles: () => rotateLogFiles,
75
80
  saveCredentials: () => saveCredentials,
81
+ updateConfigIncremental: () => updateConfigIncremental,
76
82
  validateApiToken: () => validateApiToken,
83
+ writeBaselineConfig: () => writeBaselineConfig,
77
84
  writeCodacyConfig: () => writeCodacyConfig,
78
85
  writeCodacyConfigFile: () => writeCodacyConfigFile
79
86
  });
@@ -82,6 +89,7 @@ module.exports = __toCommonJS(index_exports);
82
89
  // src/analyze.ts
83
90
  var import_os2 = __toESM(require("os"));
84
91
  var import_path4 = __toESM(require("path"));
92
+ var import_fs3 = __toESM(require("fs"));
85
93
  var import_child_process3 = require("child_process");
86
94
  var import_util = require("util");
87
95
  var import_tooling3 = require("@codacy/tooling");
@@ -154,8 +162,14 @@ var CONFIG_FILE = "codacy.config.json";
154
162
  function resolveConfigPath(repositoryRoot, configFile) {
155
163
  return configFile ? import_path.default.resolve(repositoryRoot, configFile) : import_path.default.join(repositoryRoot, CODACY_DIR, CONFIG_FILE);
156
164
  }
157
- async function readCodacyConfig(repositoryRoot, configFile) {
165
+ function resolveBaselinePath(repositoryRoot, configFile) {
158
166
  const configPath = resolveConfigPath(repositoryRoot, configFile);
167
+ const dir = import_path.default.dirname(configPath);
168
+ const ext = import_path.default.extname(configPath);
169
+ const stem = import_path.default.basename(configPath, ext);
170
+ return import_path.default.join(dir, `${stem}.baseline${ext}`);
171
+ }
172
+ async function readConfigAtPath(configPath) {
159
173
  let raw;
160
174
  try {
161
175
  raw = await import_promises.default.readFile(configPath, "utf-8");
@@ -165,10 +179,26 @@ async function readCodacyConfig(repositoryRoot, configFile) {
165
179
  }
166
180
  throw err;
167
181
  }
168
- const config = JSON.parse(raw);
182
+ const parsed = JSON.parse(raw);
183
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
184
+ throw new Error(`Invalid config at ${configPath}: expected a JSON object`);
185
+ }
186
+ const config = parsed;
169
187
  coerceParameterValues(config);
170
188
  return config;
171
189
  }
190
+ async function readCodacyConfig(repositoryRoot, configFile) {
191
+ return readConfigAtPath(resolveConfigPath(repositoryRoot, configFile));
192
+ }
193
+ async function readBaselineConfig(repositoryRoot, configFile) {
194
+ return readConfigAtPath(resolveBaselinePath(repositoryRoot, configFile));
195
+ }
196
+ async function writeBaselineConfig(repositoryRoot, config, configFile) {
197
+ const baselinePath = resolveBaselinePath(repositoryRoot, configFile);
198
+ await import_promises.default.mkdir(import_path.default.dirname(baselinePath), { recursive: true });
199
+ await import_promises.default.writeFile(baselinePath, JSON.stringify(config, null, 2) + "\n");
200
+ return baselinePath;
201
+ }
172
202
  function coerceParameterValues(config) {
173
203
  for (const tool of config.tools) {
174
204
  for (const pattern of tool.patterns) {
@@ -557,7 +587,7 @@ function routeFilesToTool(files, adapter, toolConfig) {
557
587
  }
558
588
  function filterByTargetFiles(allFiles, targetPatterns) {
559
589
  if (targetPatterns.length === 0) return allFiles;
560
- const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
590
+ const normalized = targetPatterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
561
591
  const exact = new Set(normalized);
562
592
  const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
563
593
  const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
@@ -987,6 +1017,7 @@ async function mapWithConcurrency(items, concurrency, task) {
987
1017
  }
988
1018
  async function analyze(options) {
989
1019
  const startedAt = /* @__PURE__ */ new Date();
1020
+ if (options.proxy) (0, import_tooling3.configureProxy)(options.proxy);
990
1021
  const mode = options.mode || "standard";
991
1022
  const toolTimeout = options.toolTimeout || 9e5;
992
1023
  const parallelTools = options.parallelTools || 1;
@@ -1023,6 +1054,26 @@ async function analyze(options) {
1023
1054
  []
1024
1055
  );
1025
1056
  }
1057
+ const rootStat = import_fs3.default.statSync(options.repositoryRoot, { throwIfNoEntry: false });
1058
+ if (!rootStat?.isDirectory()) {
1059
+ return buildResult(
1060
+ startedAt,
1061
+ options.repositoryRoot,
1062
+ mode,
1063
+ emptyCapability,
1064
+ [],
1065
+ [
1066
+ {
1067
+ toolId: "runner",
1068
+ phase: "requirementCheck",
1069
+ kind: "RepositoryNotFound",
1070
+ 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.`,
1071
+ level: "error"
1072
+ }
1073
+ ],
1074
+ []
1075
+ );
1076
+ }
1026
1077
  try {
1027
1078
  await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
1028
1079
  cwd: options.repositoryRoot
@@ -2110,13 +2161,13 @@ function buildConfigFileTool(toolId, source, dest, listFn) {
2110
2161
  applyToolLevelLists(tool, source, dest, listFn);
2111
2162
  return tool;
2112
2163
  }
2113
- function mergePatterns(source, dest) {
2164
+ function mergePatterns(source, dest, preferDest = false) {
2114
2165
  const sourceMap = indexByPatternId(source);
2115
2166
  const seen = /* @__PURE__ */ new Set();
2116
2167
  const result = [];
2117
2168
  for (const dp of dest) {
2118
2169
  const sp = sourceMap.get(dp.patternId);
2119
- result.push(clonePattern(sp ?? dp));
2170
+ result.push(clonePattern(preferDest ? dp : sp ?? dp));
2120
2171
  seen.add(dp.patternId);
2121
2172
  }
2122
2173
  for (const sp of source) {
@@ -2137,12 +2188,12 @@ function diffPatterns(source, dest) {
2137
2188
  const sourceIds = new Set(source.map((p) => p.patternId));
2138
2189
  return dest.filter((dp) => !sourceIds.has(dp.patternId)).map(clonePattern);
2139
2190
  }
2140
- function combineTool(op, toolId, source, dest) {
2191
+ function combineTool(op, toolId, source, dest, preferDest = false) {
2141
2192
  const listFn = op === "merge" ? unionList : intersectList;
2142
2193
  if (usesConfigFile(source) || usesConfigFile(dest)) {
2143
2194
  return buildConfigFileTool(toolId, source, dest, listFn);
2144
2195
  }
2145
- const patterns = op === "merge" ? mergePatterns(source.patterns, dest.patterns) : intersectPatterns(source.patterns, dest.patterns);
2196
+ const patterns = op === "merge" ? mergePatterns(source.patterns, dest.patterns, preferDest) : intersectPatterns(source.patterns, dest.patterns);
2146
2197
  const tool = { toolId, patterns };
2147
2198
  if (dest.useLocalConfigurationFile !== void 0) {
2148
2199
  tool.useLocalConfigurationFile = dest.useLocalConfigurationFile;
@@ -2162,13 +2213,14 @@ function buildResultConfig(dest, tools) {
2162
2213
  if (dest.includeFiles !== void 0) result.includeFiles = structuredClone(dest.includeFiles);
2163
2214
  return result;
2164
2215
  }
2165
- function mergeConfigs(source, dest) {
2216
+ function mergeConfigs(source, dest, options = {}) {
2217
+ const preferDest = options.preferDestParameters === true;
2166
2218
  const sourceMap = indexByToolId(source.tools);
2167
2219
  const seen = /* @__PURE__ */ new Set();
2168
2220
  const tools = [];
2169
2221
  for (const d of dest.tools) {
2170
2222
  const s = sourceMap.get(d.toolId);
2171
- tools.push(s ? combineTool("merge", d.toolId, s, d) : cloneTool(d));
2223
+ tools.push(s ? combineTool("merge", d.toolId, s, d, preferDest) : cloneTool(d));
2172
2224
  seen.add(d.toolId);
2173
2225
  }
2174
2226
  for (const s of source.tools) {
@@ -2226,6 +2278,39 @@ function applyConfigOperation(op, source, dest) {
2226
2278
  }
2227
2279
  }
2228
2280
  }
2281
+ function pruneEmptyTools(config) {
2282
+ const tools = config.tools.filter((t) => !isEmptyTool(t));
2283
+ if (tools.length === config.tools.length) return config;
2284
+ return { ...config, tools };
2285
+ }
2286
+ function threeWayList(base = [], current = [], next = []) {
2287
+ const baseSet = new Set(base);
2288
+ const nextSet = new Set(next);
2289
+ const removed = new Set(base.filter((x) => !nextSet.has(x)));
2290
+ const added = next.filter((x) => !baseSet.has(x));
2291
+ const kept = current.filter((x) => !removed.has(x));
2292
+ const seen = /* @__PURE__ */ new Set();
2293
+ const result = [];
2294
+ for (const x of [...kept, ...added]) {
2295
+ if (!seen.has(x)) {
2296
+ seen.add(x);
2297
+ result.push(x);
2298
+ }
2299
+ }
2300
+ return result;
2301
+ }
2302
+ function updateConfigIncremental(base, current, next) {
2303
+ const added = diffConfigs(base, next);
2304
+ const removed = diffConfigs(next, base);
2305
+ const afterRemoval = diffConfigs(removed, current);
2306
+ const merged = mergeConfigs(added, afterRemoval, { preferDestParameters: true });
2307
+ const exclude = threeWayList(base.exclude, current.exclude, next.exclude);
2308
+ const result = { ...merged };
2309
+ if (exclude.length > 0) result.exclude = exclude;
2310
+ else delete result.exclude;
2311
+ result.metadata = { ...merged.metadata, languages: [...next.metadata.languages] };
2312
+ return pruneEmptyTools(result);
2313
+ }
2229
2314
 
2230
2315
  // src/init.ts
2231
2316
  var import_promises4 = __toESM(require("fs/promises"));
@@ -2540,11 +2625,24 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2540
2625
  // --- Scala — Frameworks ---
2541
2626
  ["play-server", { displayName: "Play Framework", kind: "framework" }],
2542
2627
  ["akka-actor", { displayName: "Akka", kind: "framework" }],
2628
+ ["spray-can", { displayName: "Spray", kind: "framework" }],
2543
2629
  // --- Scala — Libraries ---
2544
2630
  ["scalatest", { displayName: "ScalaTest", kind: "library" }],
2545
2631
  ["specs2-core", { displayName: "Specs2", kind: "library" }],
2546
2632
  ["cats-core", { displayName: "Cats", kind: "library" }],
2547
2633
  ["circe-core", { displayName: "Circe", kind: "library" }],
2634
+ // --- Elixir — Frameworks ---
2635
+ ["phoenix", { displayName: "Phoenix", kind: "framework" }],
2636
+ ["plug", { displayName: "Plug", kind: "framework" }],
2637
+ ["plug_cowboy", { displayName: "Plug", kind: "framework" }],
2638
+ // --- Elixir — Libraries ---
2639
+ ["ecto", { displayName: "Ecto", kind: "library" }],
2640
+ ["ecto_sql", { displayName: "Ecto", kind: "library" }],
2641
+ ["phoenix_ecto", { displayName: "Ecto", kind: "library" }],
2642
+ // --- Crystal — Frameworks ---
2643
+ ["amber", { displayName: "Amber", kind: "framework" }],
2644
+ ["lucky", { displayName: "Lucky", kind: "framework" }],
2645
+ ["kemal", { displayName: "Kemal", kind: "framework" }],
2548
2646
  // --- Dart — Frameworks ---
2549
2647
  // `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
2550
2648
  // here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
@@ -2682,6 +2780,31 @@ var FILE_SIGNAL_RULES = [
2682
2780
  name: "suitecloud.config.js",
2683
2781
  matches: (f) => pathBasename(f) === "suitecloud.config.js",
2684
2782
  classification: { displayName: "SuiteScript", kind: "platform" }
2783
+ },
2784
+ {
2785
+ name: ".github/workflows",
2786
+ matches: (f) => f.startsWith(".github/workflows/"),
2787
+ classification: { displayName: "GitHub Actions", kind: "framework" }
2788
+ },
2789
+ {
2790
+ name: ".circleci",
2791
+ matches: (f) => f.startsWith(".circleci/"),
2792
+ classification: { displayName: "CircleCI", kind: "framework" }
2793
+ },
2794
+ {
2795
+ name: ".gitlab-ci.yml",
2796
+ matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
2797
+ classification: { displayName: "GitLab CI", kind: "framework" }
2798
+ },
2799
+ {
2800
+ name: "bitbucket-pipelines.yml",
2801
+ matches: (f) => f === "bitbucket-pipelines.yml",
2802
+ classification: { displayName: "Bitbucket Pipelines", kind: "framework" }
2803
+ },
2804
+ {
2805
+ name: "azure-pipelines.yml",
2806
+ matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
2807
+ classification: { displayName: "Azure Pipelines", kind: "framework" }
2685
2808
  }
2686
2809
  ];
2687
2810
  function detectFileSignals(files) {
@@ -2927,6 +3050,15 @@ function parseGemfile(content) {
2927
3050
  }
2928
3051
  return deps;
2929
3052
  }
3053
+ function parseMixExs(content) {
3054
+ const deps = [];
3055
+ const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
3056
+ let match;
3057
+ while ((match = depRegex.exec(content)) !== null) {
3058
+ deps.push({ name: match[1], version: match[2] || void 0 });
3059
+ }
3060
+ return deps;
3061
+ }
2930
3062
  function parseGoMod(content) {
2931
3063
  const deps = [];
2932
3064
  const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
@@ -2966,6 +3098,15 @@ function parseBuildGradle(content) {
2966
3098
  }
2967
3099
  return deps;
2968
3100
  }
3101
+ function parseBuildSbt(content) {
3102
+ const deps = [];
3103
+ const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%\s*"([^"]+)"/g;
3104
+ let match;
3105
+ while ((match = depRegex.exec(content)) !== null) {
3106
+ deps.push({ name: match[2], version: match[3] });
3107
+ }
3108
+ return deps;
3109
+ }
2969
3110
  function parseCargoToml(content) {
2970
3111
  const deps = [];
2971
3112
  let inDepsSection = false;
@@ -3046,6 +3187,41 @@ function parsePubspecYaml(content) {
3046
3187
  }
3047
3188
  return deps;
3048
3189
  }
3190
+ function parseShardYml(content) {
3191
+ const deps = [];
3192
+ let inSection = false;
3193
+ let depIndent = -1;
3194
+ let current;
3195
+ const flush = () => {
3196
+ if (current) deps.push(current);
3197
+ current = void 0;
3198
+ };
3199
+ for (const rawLine of content.split("\n")) {
3200
+ const trimmed = rawLine.trim();
3201
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
3202
+ const indent = rawLine.length - rawLine.trimStart().length;
3203
+ if (indent === 0) {
3204
+ flush();
3205
+ inSection = /^(dependencies|development_dependencies):/.test(trimmed);
3206
+ depIndent = -1;
3207
+ continue;
3208
+ }
3209
+ if (!inSection) continue;
3210
+ if (depIndent === -1) depIndent = indent;
3211
+ if (indent === depIndent) {
3212
+ flush();
3213
+ const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
3214
+ current = m ? { name: m[1] } : void 0;
3215
+ continue;
3216
+ }
3217
+ if (indent > depIndent && current) {
3218
+ const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
3219
+ if (v) current.version = v[1];
3220
+ }
3221
+ }
3222
+ flush();
3223
+ return deps;
3224
+ }
3049
3225
  function parseXmlDependencyElements(content, elementRegex, nameAttr) {
3050
3226
  const deps = [];
3051
3227
  const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
@@ -3080,11 +3256,14 @@ var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
3080
3256
  ["pom.xml", parsePomXml],
3081
3257
  ["build.gradle", parseBuildGradle],
3082
3258
  ["build.gradle.kts", parseBuildGradle],
3259
+ ["build.sbt", parseBuildSbt],
3083
3260
  ["Cargo.toml", parseCargoToml],
3084
3261
  ["composer.json", parseComposerJson],
3085
3262
  ["Podfile", parsePodfile],
3086
3263
  ["Package.swift", parsePackageSwift],
3087
3264
  ["pubspec.yaml", parsePubspecYaml],
3265
+ ["mix.exs", parseMixExs],
3266
+ ["shard.yml", parseShardYml],
3088
3267
  ["packages.config", parsePackagesConfig],
3089
3268
  ["Directory.Packages.props", parseCsproj]
3090
3269
  ]);
@@ -7133,6 +7312,11 @@ async function initContainerConfig(repositoryRoot, adapters) {
7133
7312
  }
7134
7313
  return createCodacyConfig({ source: "container", tools, includeFiles });
7135
7314
  }
7315
+ function hasMatchingFiles(files, filePatterns) {
7316
+ const matcher = new CompiledMatcher(filePatterns);
7317
+ if (matcher.matchAll) return files.length > 0;
7318
+ return files.some((file) => matcher.matches(file, file.split("/").pop() ?? ""));
7319
+ }
7136
7320
  async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
7137
7321
  const discoveryResult = await discover(repositoryRoot);
7138
7322
  onDiscoveryComplete?.(discoveryResult);
@@ -7142,6 +7326,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7142
7326
  for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
7143
7327
  const repoLanguages = new Set(discoveryResult.languages);
7144
7328
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
7329
+ const repoFiles = listRepoFiles(repositoryRoot);
7145
7330
  const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
7146
7331
  const candidates = [];
7147
7332
  for (const adapter of adapters) {
@@ -7149,6 +7334,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7149
7334
  const hasOverlap = adapter.languages.some((lang) => repoLanguages.has(lang));
7150
7335
  if (!hasOverlap) continue;
7151
7336
  }
7337
+ if (!hasMatchingFiles(repoFiles, adapter.filePatterns)) continue;
7152
7338
  candidates.push(adapter);
7153
7339
  }
7154
7340
  const deduped = deduplicateToolFamilies(candidates, localConfigs);
@@ -7192,6 +7378,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7192
7378
  }
7193
7379
  const family = toolFamily(desc.id);
7194
7380
  if (selectedFamilies.has(family)) continue;
7381
+ if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
7195
7382
  const patternsMetadata = loadUnsupportedPatterns(desc.id);
7196
7383
  if (patternsMetadata.length === 0) continue;
7197
7384
  const selectedPatterns = filterAutoPatterns(
@@ -7410,6 +7597,9 @@ function parseAutoFilters(raw) {
7410
7597
  return { filters: { severities, categories }, unknown };
7411
7598
  }
7412
7599
 
7600
+ // src/index.ts
7601
+ var import_tooling6 = require("@codacy/tooling");
7602
+
7413
7603
  // src/credentials.ts
7414
7604
  var import_crypto = __toESM(require("crypto"));
7415
7605
  var import_promises5 = __toESM(require("fs/promises"));
@@ -7482,6 +7672,7 @@ async function resolveApiToken() {
7482
7672
  clearAdapters,
7483
7673
  computePatternSummary,
7484
7674
  configureApiToken,
7675
+ configureProxy,
7485
7676
  createCodacyConfig,
7486
7677
  createLogger,
7487
7678
  createNullLogger,
@@ -7508,17 +7699,23 @@ async function resolveApiToken() {
7508
7699
  loadCredentials,
7509
7700
  mergeConfigs,
7510
7701
  parseAutoFilters,
7702
+ pruneEmptyTools,
7703
+ readBaselineConfig,
7511
7704
  readCodacyConfig,
7512
7705
  readCodacyYaml,
7513
7706
  registerAdapter,
7514
7707
  registerLazyAdapter,
7515
7708
  registerUnsupportedTool,
7516
7709
  resolveApiToken,
7710
+ resolveBaselinePath,
7517
7711
  resolveConfigPath,
7518
7712
  resolveEngineToAdapter,
7713
+ resolveProxyConfig,
7519
7714
  rotateLogFiles,
7520
7715
  saveCredentials,
7716
+ updateConfigIncremental,
7521
7717
  validateApiToken,
7718
+ writeBaselineConfig,
7522
7719
  writeCodacyConfig,
7523
7720
  writeCodacyConfigFile
7524
7721
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.13.0",
3
+ "version": "0.15.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.13.0"
15
+ "@codacy/tooling": "0.15.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.13.0"
19
+ "@codacy/tools-dummy-1": "0.15.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",