@codacy/analysis-runner 0.13.0 → 0.14.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
  });
@@ -154,8 +161,14 @@ var CONFIG_FILE = "codacy.config.json";
154
161
  function resolveConfigPath(repositoryRoot, configFile) {
155
162
  return configFile ? import_path.default.resolve(repositoryRoot, configFile) : import_path.default.join(repositoryRoot, CODACY_DIR, CONFIG_FILE);
156
163
  }
157
- async function readCodacyConfig(repositoryRoot, configFile) {
164
+ function resolveBaselinePath(repositoryRoot, configFile) {
158
165
  const configPath = resolveConfigPath(repositoryRoot, configFile);
166
+ const dir = import_path.default.dirname(configPath);
167
+ const ext = import_path.default.extname(configPath);
168
+ const stem = import_path.default.basename(configPath, ext);
169
+ return import_path.default.join(dir, `${stem}.baseline${ext}`);
170
+ }
171
+ async function readConfigAtPath(configPath) {
159
172
  let raw;
160
173
  try {
161
174
  raw = await import_promises.default.readFile(configPath, "utf-8");
@@ -165,10 +178,26 @@ async function readCodacyConfig(repositoryRoot, configFile) {
165
178
  }
166
179
  throw err;
167
180
  }
168
- const config = JSON.parse(raw);
181
+ const parsed = JSON.parse(raw);
182
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
183
+ throw new Error(`Invalid config at ${configPath}: expected a JSON object`);
184
+ }
185
+ const config = parsed;
169
186
  coerceParameterValues(config);
170
187
  return config;
171
188
  }
189
+ async function readCodacyConfig(repositoryRoot, configFile) {
190
+ return readConfigAtPath(resolveConfigPath(repositoryRoot, configFile));
191
+ }
192
+ async function readBaselineConfig(repositoryRoot, configFile) {
193
+ return readConfigAtPath(resolveBaselinePath(repositoryRoot, configFile));
194
+ }
195
+ async function writeBaselineConfig(repositoryRoot, config, configFile) {
196
+ const baselinePath = resolveBaselinePath(repositoryRoot, configFile);
197
+ await import_promises.default.mkdir(import_path.default.dirname(baselinePath), { recursive: true });
198
+ await import_promises.default.writeFile(baselinePath, JSON.stringify(config, null, 2) + "\n");
199
+ return baselinePath;
200
+ }
172
201
  function coerceParameterValues(config) {
173
202
  for (const tool of config.tools) {
174
203
  for (const pattern of tool.patterns) {
@@ -987,6 +1016,7 @@ async function mapWithConcurrency(items, concurrency, task) {
987
1016
  }
988
1017
  async function analyze(options) {
989
1018
  const startedAt = /* @__PURE__ */ new Date();
1019
+ if (options.proxy) (0, import_tooling3.configureProxy)(options.proxy);
990
1020
  const mode = options.mode || "standard";
991
1021
  const toolTimeout = options.toolTimeout || 9e5;
992
1022
  const parallelTools = options.parallelTools || 1;
@@ -2110,13 +2140,13 @@ function buildConfigFileTool(toolId, source, dest, listFn) {
2110
2140
  applyToolLevelLists(tool, source, dest, listFn);
2111
2141
  return tool;
2112
2142
  }
2113
- function mergePatterns(source, dest) {
2143
+ function mergePatterns(source, dest, preferDest = false) {
2114
2144
  const sourceMap = indexByPatternId(source);
2115
2145
  const seen = /* @__PURE__ */ new Set();
2116
2146
  const result = [];
2117
2147
  for (const dp of dest) {
2118
2148
  const sp = sourceMap.get(dp.patternId);
2119
- result.push(clonePattern(sp ?? dp));
2149
+ result.push(clonePattern(preferDest ? dp : sp ?? dp));
2120
2150
  seen.add(dp.patternId);
2121
2151
  }
2122
2152
  for (const sp of source) {
@@ -2137,12 +2167,12 @@ function diffPatterns(source, dest) {
2137
2167
  const sourceIds = new Set(source.map((p) => p.patternId));
2138
2168
  return dest.filter((dp) => !sourceIds.has(dp.patternId)).map(clonePattern);
2139
2169
  }
2140
- function combineTool(op, toolId, source, dest) {
2170
+ function combineTool(op, toolId, source, dest, preferDest = false) {
2141
2171
  const listFn = op === "merge" ? unionList : intersectList;
2142
2172
  if (usesConfigFile(source) || usesConfigFile(dest)) {
2143
2173
  return buildConfigFileTool(toolId, source, dest, listFn);
2144
2174
  }
2145
- const patterns = op === "merge" ? mergePatterns(source.patterns, dest.patterns) : intersectPatterns(source.patterns, dest.patterns);
2175
+ const patterns = op === "merge" ? mergePatterns(source.patterns, dest.patterns, preferDest) : intersectPatterns(source.patterns, dest.patterns);
2146
2176
  const tool = { toolId, patterns };
2147
2177
  if (dest.useLocalConfigurationFile !== void 0) {
2148
2178
  tool.useLocalConfigurationFile = dest.useLocalConfigurationFile;
@@ -2162,13 +2192,14 @@ function buildResultConfig(dest, tools) {
2162
2192
  if (dest.includeFiles !== void 0) result.includeFiles = structuredClone(dest.includeFiles);
2163
2193
  return result;
2164
2194
  }
2165
- function mergeConfigs(source, dest) {
2195
+ function mergeConfigs(source, dest, options = {}) {
2196
+ const preferDest = options.preferDestParameters === true;
2166
2197
  const sourceMap = indexByToolId(source.tools);
2167
2198
  const seen = /* @__PURE__ */ new Set();
2168
2199
  const tools = [];
2169
2200
  for (const d of dest.tools) {
2170
2201
  const s = sourceMap.get(d.toolId);
2171
- tools.push(s ? combineTool("merge", d.toolId, s, d) : cloneTool(d));
2202
+ tools.push(s ? combineTool("merge", d.toolId, s, d, preferDest) : cloneTool(d));
2172
2203
  seen.add(d.toolId);
2173
2204
  }
2174
2205
  for (const s of source.tools) {
@@ -2226,6 +2257,39 @@ function applyConfigOperation(op, source, dest) {
2226
2257
  }
2227
2258
  }
2228
2259
  }
2260
+ function pruneEmptyTools(config) {
2261
+ const tools = config.tools.filter((t) => !isEmptyTool(t));
2262
+ if (tools.length === config.tools.length) return config;
2263
+ return { ...config, tools };
2264
+ }
2265
+ function threeWayList(base = [], current = [], next = []) {
2266
+ const baseSet = new Set(base);
2267
+ const nextSet = new Set(next);
2268
+ const removed = new Set(base.filter((x) => !nextSet.has(x)));
2269
+ const added = next.filter((x) => !baseSet.has(x));
2270
+ const kept = current.filter((x) => !removed.has(x));
2271
+ const seen = /* @__PURE__ */ new Set();
2272
+ const result = [];
2273
+ for (const x of [...kept, ...added]) {
2274
+ if (!seen.has(x)) {
2275
+ seen.add(x);
2276
+ result.push(x);
2277
+ }
2278
+ }
2279
+ return result;
2280
+ }
2281
+ function updateConfigIncremental(base, current, next) {
2282
+ const added = diffConfigs(base, next);
2283
+ const removed = diffConfigs(next, base);
2284
+ const afterRemoval = diffConfigs(removed, current);
2285
+ const merged = mergeConfigs(added, afterRemoval, { preferDestParameters: true });
2286
+ const exclude = threeWayList(base.exclude, current.exclude, next.exclude);
2287
+ const result = { ...merged };
2288
+ if (exclude.length > 0) result.exclude = exclude;
2289
+ else delete result.exclude;
2290
+ result.metadata = { ...merged.metadata, languages: [...next.metadata.languages] };
2291
+ return pruneEmptyTools(result);
2292
+ }
2229
2293
 
2230
2294
  // src/init.ts
2231
2295
  var import_promises4 = __toESM(require("fs/promises"));
@@ -7410,6 +7474,9 @@ function parseAutoFilters(raw) {
7410
7474
  return { filters: { severities, categories }, unknown };
7411
7475
  }
7412
7476
 
7477
+ // src/index.ts
7478
+ var import_tooling6 = require("@codacy/tooling");
7479
+
7413
7480
  // src/credentials.ts
7414
7481
  var import_crypto = __toESM(require("crypto"));
7415
7482
  var import_promises5 = __toESM(require("fs/promises"));
@@ -7482,6 +7549,7 @@ async function resolveApiToken() {
7482
7549
  clearAdapters,
7483
7550
  computePatternSummary,
7484
7551
  configureApiToken,
7552
+ configureProxy,
7485
7553
  createCodacyConfig,
7486
7554
  createLogger,
7487
7555
  createNullLogger,
@@ -7508,17 +7576,23 @@ async function resolveApiToken() {
7508
7576
  loadCredentials,
7509
7577
  mergeConfigs,
7510
7578
  parseAutoFilters,
7579
+ pruneEmptyTools,
7580
+ readBaselineConfig,
7511
7581
  readCodacyConfig,
7512
7582
  readCodacyYaml,
7513
7583
  registerAdapter,
7514
7584
  registerLazyAdapter,
7515
7585
  registerUnsupportedTool,
7516
7586
  resolveApiToken,
7587
+ resolveBaselinePath,
7517
7588
  resolveConfigPath,
7518
7589
  resolveEngineToAdapter,
7590
+ resolveProxyConfig,
7519
7591
  rotateLogFiles,
7520
7592
  saveCredentials,
7593
+ updateConfigIncremental,
7521
7594
  validateApiToken,
7595
+ writeBaselineConfig,
7522
7596
  writeCodacyConfig,
7523
7597
  writeCodacyConfigFile
7524
7598
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.13.0",
3
+ "version": "0.14.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.14.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.14.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",