@codacy/analysis-runner 0.4.0 → 0.6.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,4 +1,4 @@
1
- import { OutputFormat, Logger, AnalysisResult, ToolAdapter, CodacyConfig, ConfigurationFileCheckResult, CodacyToolConfig, Language, LogLevel } from '@codacy/tooling';
1
+ import { OutputFormat, Logger, AnalysisResult, ToolAdapter, AdapterDescriptor, CodacyConfig, ConfigurationFileCheckResult, CodacyToolConfig, Language, LogLevel } from '@codacy/tooling';
2
2
  export { AnalysisResult, OutputFormat } from '@codacy/tooling';
3
3
 
4
4
  /**
@@ -60,16 +60,55 @@ interface AnalyzeOptions {
60
60
  declare function analyze(options: AnalyzeOptions): Promise<AnalysisResult>;
61
61
 
62
62
  /**
63
- * Tool adapter registry — stores registered adapters for discovery by the runner.
63
+ * Tool adapter registry — stores adapters for discovery by the runner.
64
64
  *
65
- * Adapters must be registered before calling analyze(). The registry is a simple
66
- * in-memory map keyed by adapter ID. clearAdapters() is provided for testing.
65
+ * Supports two registration modes:
66
+ * - **Eager** (`registerAdapter`): stores a fully resolved adapter. Used by tests
67
+ * and programmatic consumers that already have the adapter object.
68
+ * - **Lazy** (`registerLazyAdapter`): stores lightweight metadata plus a factory
69
+ * function that dynamically imports the full adapter module on first use. Used
70
+ * by the CLI to avoid loading all 30+ adapter modules at startup.
71
+ *
72
+ * `clearAdapters()` is provided for testing.
67
73
  */
68
74
 
69
- /** Register a tool adapter so the runner can discover and invoke it. */
75
+ /** Async factory that loads and returns a full ToolAdapter. */
76
+ type AdapterFactory = () => Promise<Readonly<ToolAdapter>>;
77
+ /**
78
+ * Register a fully resolved tool adapter (eager mode).
79
+ *
80
+ * The adapter is available immediately — no dynamic import needed. This is the
81
+ * path used by tests and by consumers that import adapters directly.
82
+ */
70
83
  declare function registerAdapter(adapter: Readonly<ToolAdapter>): void;
71
- /** Returns all registered adapters, optionally filtered by tool ID list. */
72
- declare function getRegisteredAdapters(toolIds?: string[]): Readonly<ToolAdapter>[];
84
+ /**
85
+ * Register an adapter lazily via its descriptor and a factory function.
86
+ *
87
+ * The factory is only called when the adapter is first needed for analysis.
88
+ * The descriptor provides enough information for fuzzy matching, file routing,
89
+ * and inspect-mode metadata display without loading the full module.
90
+ */
91
+ declare function registerLazyAdapter(descriptor: AdapterDescriptor, factory: AdapterFactory): void;
92
+ /**
93
+ * Returns all registered adapters, optionally filtered by tool ID list.
94
+ *
95
+ * This is async because lazy entries may need to import their module on first
96
+ * access. For callers that only need metadata, use `getRegisteredDescriptors`.
97
+ */
98
+ declare function getRegisteredAdapters(toolIds?: string[]): Promise<Readonly<ToolAdapter>[]>;
99
+ /**
100
+ * Returns descriptors for all registered adapters (sync, no module loading).
101
+ *
102
+ * Useful when only metadata is needed — e.g. file routing, fuzzy matching,
103
+ * or init-mode tool discovery.
104
+ */
105
+ declare function getRegisteredDescriptors(toolIds?: string[]): AdapterDescriptor[];
106
+ /**
107
+ * Loads and returns a single adapter by its ID (exact match).
108
+ *
109
+ * Returns undefined if no adapter with that ID is registered.
110
+ */
111
+ declare function getAdapter(toolId: string): Promise<Readonly<ToolAdapter> | undefined>;
73
112
  /** Remove all registered adapters. Used in tests to reset state between runs. */
74
113
  declare function clearAdapters(): void;
75
114
 
@@ -206,6 +245,10 @@ interface InitRemoteStats {
206
245
  matchedAdapters: number;
207
246
  /** Whether a .codacy.yaml file was found and its exclude paths were applied. */
208
247
  codacyYamlApplied: boolean;
248
+ /** Number of cloud-ignored files fetched from the Codacy API. */
249
+ ignoredFilesCount: number;
250
+ /** Whether the fetch was stopped because it hit the 1000-file cap. */
251
+ ignoredFilesCapped: boolean;
209
252
  }
210
253
  /**
211
254
  * `--remote <provider> <org> <repo>` mode.
@@ -219,8 +262,10 @@ interface InitRemoteStats {
219
262
  * @param onToolProcessed - Optional callback invoked after each tool's patterns are
220
263
  * fetched. Receives the adapter's display name, the number of patterns configured, and
221
264
  * whether the tool uses a local configuration file. Useful for real-time CLI progress.
265
+ * @param onIgnoredFilesFetched - Optional callback invoked after cloud-ignored files are
266
+ * fetched. Receives the count and whether the 1000-file cap was reached.
222
267
  */
223
- declare function initRemoteConfig(repositoryRoot: string, provider: string, organization: string, repositoryName: string, adapters: Readonly<ToolAdapter>[], apiToken: string, onRemoteToolsFound?: (totalEnabledTools: number, matchedAdapters: number) => void, onToolProcessed?: (displayName: string, patternCount: number, usesConfigFile: boolean) => void): Promise<{
268
+ declare function initRemoteConfig(repositoryRoot: string, provider: string, organization: string, repositoryName: string, adapters: Readonly<ToolAdapter>[], apiToken: string, onRemoteToolsFound?: (totalEnabledTools: number, matchedAdapters: number) => void, onToolProcessed?: (displayName: string, patternCount: number, usesConfigFile: boolean) => void, onIgnoredFilesFetched?: (count: number, capped: boolean) => void): Promise<{
224
269
  config: CodacyConfig;
225
270
  stats: InitRemoteStats;
226
271
  }>;
@@ -334,6 +379,15 @@ declare function listPrFiles(repositoryRoot: string): string[];
334
379
  * follows cursors until the response contains no next cursor.
335
380
  */
336
381
 
382
+ /** Result of fetching cloud-ignored files, with pagination cap. */
383
+ interface IgnoredFilesResult {
384
+ /** File paths ignored in Codacy Cloud. */
385
+ files: string[];
386
+ /** Total number of files fetched (same as files.length). */
387
+ totalFetched: number;
388
+ /** Whether the fetch was stopped because it hit the 1000-file cap. */
389
+ capped: boolean;
390
+ }
337
391
  /**
338
392
  * Configures the API token used for authenticated requests.
339
393
  * Must be called before `getRepository`, `listRepositoryTools`, or
@@ -350,6 +404,13 @@ declare function validateApiToken(token: string): Promise<{
350
404
  name?: string;
351
405
  email?: string;
352
406
  }>;
407
+ /**
408
+ * Fetches files individually ignored in Codacy Cloud for a repository.
409
+ * Uses cursor-based pagination capped at 10 pages (1000 files max) to avoid
410
+ * unbounded fetching for repositories with very large ignore lists.
411
+ * Requires `configureApiToken()` to have been called.
412
+ */
413
+ declare function listIgnoredFiles(provider: string, org: string, repo: string): Promise<IgnoredFilesResult>;
353
414
 
354
415
  /**
355
416
  * Structured file + stderr logger for the analysis pipeline.
@@ -403,4 +464,4 @@ declare function rotateLogFiles(logsDir: string, maxFiles: number): void;
403
464
  */
404
465
  declare function createLogger(options?: LoggerOptions): Logger;
405
466
 
406
- export { type AnalyzeOptions, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ExecutionMode, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, analyze, applyCodacyYamlExcludes, buildToolConfig, clearAdapters, configureApiToken, createCodacyConfig, createLogger, detectLanguages, formatOutput, formatTextSummary, getRegisteredAdapters, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, listDiffFiles, listPrFiles, listStagedFiles, readCodacyConfig, readCodacyYaml, registerAdapter, resolveEngineToAdapter, rotateLogFiles, validateApiToken, writeCodacyConfig };
467
+ export { type AdapterFactory, type AnalyzeOptions, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ExecutionMode, type IgnoredFilesResult, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, analyze, applyCodacyYamlExcludes, buildToolConfig, clearAdapters, configureApiToken, createCodacyConfig, createLogger, detectLanguages, formatOutput, formatTextSummary, getAdapter, getRegisteredAdapters, getRegisteredDescriptors, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, resolveEngineToAdapter, rotateLogFiles, validateApiToken, writeCodacyConfig };
package/dist/index.js CHANGED
@@ -40,17 +40,21 @@ __export(index_exports, {
40
40
  detectLanguages: () => detectLanguages,
41
41
  formatOutput: () => formatOutput,
42
42
  formatTextSummary: () => formatTextSummary,
43
+ getAdapter: () => getAdapter,
43
44
  getRegisteredAdapters: () => getRegisteredAdapters,
45
+ getRegisteredDescriptors: () => getRegisteredDescriptors,
44
46
  initContainerConfig: () => initContainerConfig,
45
47
  initDefaultConfig: () => initDefaultConfig,
46
48
  initLocalConfig: () => initLocalConfig,
47
49
  initRemoteConfig: () => initRemoteConfig,
48
50
  listDiffFiles: () => listDiffFiles,
51
+ listIgnoredFiles: () => listIgnoredFiles,
49
52
  listPrFiles: () => listPrFiles,
50
53
  listStagedFiles: () => listStagedFiles,
51
54
  readCodacyConfig: () => readCodacyConfig,
52
55
  readCodacyYaml: () => readCodacyYaml,
53
56
  registerAdapter: () => registerAdapter,
57
+ registerLazyAdapter: () => registerLazyAdapter,
54
58
  resolveEngineToAdapter: () => resolveEngineToAdapter,
55
59
  rotateLogFiles: () => rotateLogFiles,
56
60
  validateApiToken: () => validateApiToken,
@@ -62,29 +66,56 @@ module.exports = __toCommonJS(index_exports);
62
66
  var import_os = __toESM(require("os"));
63
67
  var import_path3 = __toESM(require("path"));
64
68
  var import_child_process2 = require("child_process");
69
+ var import_util = require("util");
70
+ var import_tooling3 = require("@codacy/tooling");
65
71
 
66
72
  // src/registry.ts
67
- var adapters = /* @__PURE__ */ new Map();
73
+ var import_tooling = require("@codacy/tooling");
74
+ var entries = /* @__PURE__ */ new Map();
68
75
  function registerAdapter(adapter) {
69
- adapters.set(adapter.id, adapter);
76
+ entries.set(adapter.id, {
77
+ descriptor: (0, import_tooling.extractDescriptor)(adapter),
78
+ factory: async () => adapter,
79
+ resolved: adapter
80
+ });
81
+ }
82
+ function registerLazyAdapter(descriptor, factory) {
83
+ entries.set(descriptor.id, { descriptor, factory });
70
84
  }
71
- function resolveAdapter(input) {
72
- const exact = adapters.get(input);
85
+ function resolveEntry(input) {
86
+ const exact = entries.get(input);
73
87
  if (exact) return exact;
74
88
  const needle = input.toLowerCase();
75
- const matches = Array.from(adapters.values()).filter((a) => {
76
- return a.id.toLowerCase().includes(needle) || a.displayName.toLowerCase().includes(needle);
89
+ const matches = Array.from(entries.values()).filter((e) => {
90
+ return e.descriptor.id.toLowerCase().includes(needle) || e.descriptor.displayName.toLowerCase().includes(needle);
77
91
  });
78
92
  return matches.length === 1 ? matches[0] : void 0;
79
93
  }
80
- function getRegisteredAdapters(toolIds) {
94
+ async function loadAdapter(entry) {
95
+ if (entry.resolved) return entry.resolved;
96
+ entry.resolved = await entry.factory();
97
+ return entry.resolved;
98
+ }
99
+ async function getRegisteredAdapters(toolIds) {
81
100
  if (toolIds && toolIds.length > 0) {
82
- return toolIds.map((id) => resolveAdapter(id)).filter((a) => a !== void 0);
101
+ const matched = toolIds.map((id) => resolveEntry(id)).filter((e) => e !== void 0);
102
+ return Promise.all(matched.map(loadAdapter));
83
103
  }
84
- return Array.from(adapters.values());
104
+ return Promise.all(Array.from(entries.values()).map(loadAdapter));
105
+ }
106
+ function getRegisteredDescriptors(toolIds) {
107
+ if (toolIds && toolIds.length > 0) {
108
+ return toolIds.map((id) => resolveEntry(id)).filter((e) => e !== void 0).map((e) => e.descriptor);
109
+ }
110
+ return Array.from(entries.values()).map((e) => e.descriptor);
111
+ }
112
+ async function getAdapter(toolId) {
113
+ const entry = entries.get(toolId);
114
+ if (!entry) return void 0;
115
+ return loadAdapter(entry);
85
116
  }
86
117
  function clearAdapters() {
87
- adapters.clear();
118
+ entries.clear();
88
119
  }
89
120
 
90
121
  // src/config.ts
@@ -330,6 +361,7 @@ function filterByTargetFiles(allFiles, targetPatterns) {
330
361
  }
331
362
 
332
363
  // src/executor.ts
364
+ var import_tooling2 = require("@codacy/tooling");
333
365
  async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
334
366
  const startTime = Date.now();
335
367
  ctx.logger.debug("runTool started", {
@@ -340,7 +372,7 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
340
372
  try {
341
373
  const timeoutPromise = new Promise((_resolve, reject) => {
342
374
  setTimeout(
343
- () => reject(new Error(`TIMEOUT: Tool ${adapter.id} exceeded ${timeoutMs}ms`)),
375
+ () => reject(new Error(`TIMEOUT: Tool ${adapter.id} exceeded ${(0, import_tooling2.formatDuration)(timeoutMs)}`)),
344
376
  timeoutMs
345
377
  );
346
378
  });
@@ -376,7 +408,7 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
376
408
  toolId: adapter.id,
377
409
  phase: "toolInvoke",
378
410
  kind: isTimeout ? "Timeout" : "InvocationError",
379
- message: isTimeout ? `Tool ${adapter.id} exceeded timeout of ${timeoutMs}ms` : `Tool ${adapter.id} failed: ${err instanceof Error ? err.message : String(err)}`,
411
+ message: isTimeout ? `Tool ${adapter.id} exceeded timeout of ${(0, import_tooling2.formatDuration)(timeoutMs)}` : `Tool ${adapter.id} failed: ${err instanceof Error ? err.message : String(err)}`,
380
412
  level: "error"
381
413
  }
382
414
  ],
@@ -429,6 +461,7 @@ async function runToolsParallel(tools, timeoutMs, concurrency, callbacks) {
429
461
  }
430
462
 
431
463
  // src/analyze.ts
464
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
432
465
  var nullLogger = {
433
466
  debug: () => {
434
467
  },
@@ -451,26 +484,20 @@ async function analyze(options) {
451
484
  checkedAt: (/* @__PURE__ */ new Date()).toISOString()
452
485
  };
453
486
  try {
454
- (0, import_child_process2.execSync)("git rev-parse --is-inside-work-tree", {
455
- cwd: options.repositoryRoot,
456
- stdio: "pipe"
487
+ await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
488
+ cwd: options.repositoryRoot
457
489
  });
458
- } catch {
490
+ } catch (err) {
491
+ const isGitMissing = err instanceof Error && "code" in err && err.code === "ENOENT";
492
+ const message = isGitMissing ? "git is not installed. Codacy analysis requires git for file discovery." : `${options.repositoryRoot} is not a git repository. Codacy analysis requires a git repository for file discovery. Run \`git init\` to initialize one.`;
493
+ const kind = isGitMissing ? "GitNotInstalled" : "NotAGitRepository";
459
494
  return buildResult(
460
495
  startedAt,
461
496
  options.repositoryRoot,
462
497
  mode,
463
498
  emptyCapability,
464
499
  [],
465
- [
466
- {
467
- toolId: "runner",
468
- phase: "requirementCheck",
469
- kind: "NotAGitRepository",
470
- message: `${options.repositoryRoot} is not a git repository. Codacy analysis requires a git repository for file discovery. Run \`git init\` to initialize one.`,
471
- level: "error"
472
- }
473
- ],
500
+ [{ toolId: "runner", phase: "requirementCheck", kind, message, level: "error" }],
474
501
  []
475
502
  );
476
503
  }
@@ -515,8 +542,9 @@ async function analyze(options) {
515
542
  []
516
543
  );
517
544
  }
518
- const adapters2 = getRegisteredAdapters(options.tools);
519
- logger.info("Pipeline started", { mode, toolCount: adapters2.length, parallelTools, toolTimeout });
545
+ const requestedTools = options.tools ?? codacyConfig.tools.map((t) => t.toolId);
546
+ const adapters = await getRegisteredAdapters(requestedTools);
547
+ logger.info("Pipeline started", { mode, toolCount: adapters.length, parallelTools, toolTimeout });
520
548
  const globalDir = import_path3.default.join(import_os.default.homedir(), ".codacy");
521
549
  const baseCtx = {
522
550
  repositoryRoot: options.repositoryRoot,
@@ -528,7 +556,7 @@ async function analyze(options) {
528
556
  const ready = [];
529
557
  const unavailable = [];
530
558
  const availabilityTimeout = 6e4;
531
- for (const adapter of adapters2) {
559
+ for (const adapter of adapters) {
532
560
  const ctx = makeToolContext(baseCtx, adapter, []);
533
561
  const checkStart = Date.now();
534
562
  logger.debug("Checking availability", { toolId: adapter.id });
@@ -537,7 +565,11 @@ async function analyze(options) {
537
565
  adapter.checkAvailability(ctx),
538
566
  new Promise(
539
567
  (_, reject) => setTimeout(
540
- () => reject(new Error(`checkAvailability timed out after ${availabilityTimeout}ms`)),
568
+ () => reject(
569
+ new Error(
570
+ `checkAvailability timed out after ${(0, import_tooling3.formatDuration)(availabilityTimeout)}`
571
+ )
572
+ ),
541
573
  availabilityTimeout
542
574
  )
543
575
  )
@@ -600,7 +632,7 @@ async function analyze(options) {
600
632
  const installErrors = [];
601
633
  for (let i = capability.unavailable.length - 1; i >= 0; i--) {
602
634
  const status = capability.unavailable[i];
603
- const adapter = adapters2.find((a) => a.id === status.toolId);
635
+ const adapter = adapters.find((a) => a.id === status.toolId);
604
636
  if (!adapter) continue;
605
637
  const ctx = makeToolContext(baseCtx, adapter, []);
606
638
  logger.info(`${adapter.displayName} is missing, attempting install...`);
@@ -674,7 +706,7 @@ async function analyze(options) {
674
706
  }
675
707
  if (mode === "inspect") {
676
708
  for (const tool of capability.ready) {
677
- const adapter = adapters2.find((a) => a.id === tool.toolId);
709
+ const adapter = adapters.find((a) => a.id === tool.toolId);
678
710
  if (!adapter) continue;
679
711
  const ctx = makeToolContext(baseCtx, adapter, []);
680
712
  const availability = await adapter.checkAvailability(ctx).catch(() => null);
@@ -741,7 +773,7 @@ async function analyze(options) {
741
773
  allFiles = filterByTargetFiles(allFiles, options.files);
742
774
  logger.debug("Filtered by target files", { afterFilter: allFiles.length });
743
775
  }
744
- const readyAdapters = adapters2.filter((a) => capability.ready.some((r) => r.toolId === a.id));
776
+ const readyAdapters = adapters.filter((a) => capability.ready.some((r) => r.toolId === a.id));
745
777
  const toolRuns = [];
746
778
  for (const adapter of readyAdapters) {
747
779
  const toolConfig = getToolConfig(codacyConfig, adapter.id);
@@ -769,6 +801,10 @@ async function analyze(options) {
769
801
  logger.debug("No files routed, skipping", { toolId: adapter.id });
770
802
  continue;
771
803
  }
804
+ if (!codacyToolConfig.useLocalConfigurationFile && codacyToolConfig.patterns.length === 0) {
805
+ logger.debug("No patterns and no local config, skipping", { toolId: adapter.id });
806
+ continue;
807
+ }
772
808
  logger.debug("Files routed to tool", { toolId: adapter.id, fileCount: toolFiles.length });
773
809
  logger.debug("Tool config resolved", {
774
810
  toolId: adapter.id,
@@ -852,6 +888,7 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
852
888
 
853
889
  // src/format.ts
854
890
  var import_path4 = __toESM(require("path"));
891
+ var import_tooling4 = require("@codacy/tooling");
855
892
  var import_ansis = __toESM(require("ansis"));
856
893
  var import_cli_table3 = __toESM(require("cli-table3"));
857
894
  function formatOutput(result, format) {
@@ -1033,7 +1070,7 @@ function formatTextSummary(result, isInspect) {
1033
1070
  } else {
1034
1071
  const headerLine = `Analysis complete: ${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`;
1035
1072
  lines.push(totalIssues > 0 ? import_ansis.default.bold.red(headerLine) : import_ansis.default.bold(headerLine));
1036
- lines.push(`${toolCount} tool${toolCount !== 1 ? "s" : ""} ran in ${duration}ms`);
1073
+ lines.push(`${toolCount} tool${toolCount !== 1 ? "s" : ""} ran in ${(0, import_tooling4.formatDuration)(duration)}`);
1037
1074
  }
1038
1075
  if (totalErrors > 0) {
1039
1076
  lines.push(import_ansis.default.red(`${totalErrors} error${totalErrors !== 1 ? "s" : ""} occurred`));
@@ -1094,7 +1131,7 @@ function formatTextSummary(result, isInspect) {
1094
1131
  formatConfigCell(tool, result.metadata.repositoryRoot),
1095
1132
  filesCell,
1096
1133
  issueCount > 0 ? import_ansis.default.bold.red(String(issueCount)) : String(issueCount),
1097
- `${durationMs}ms`
1134
+ (0, import_tooling4.formatDuration)(durationMs)
1098
1135
  ]);
1099
1136
  }
1100
1137
  for (const tool of unavailableTools) {
@@ -1209,7 +1246,7 @@ function colorSeverity(severity) {
1209
1246
  var import_promises3 = __toESM(require("fs/promises"));
1210
1247
  var import_path6 = __toESM(require("path"));
1211
1248
  var import_child_process3 = require("child_process");
1212
- var import_tooling = require("@codacy/tooling");
1249
+ var import_tooling5 = require("@codacy/tooling");
1213
1250
 
1214
1251
  // src/codacy-yaml.ts
1215
1252
  var import_promises2 = __toESM(require("fs/promises"));
@@ -1252,16 +1289,16 @@ function extractStringArray(value) {
1252
1289
  if (!Array.isArray(value)) return [];
1253
1290
  return value.filter((v) => typeof v === "string");
1254
1291
  }
1255
- function resolveEngineToAdapter(engineName, adapters2) {
1256
- const exact = adapters2.find((a) => a.id === engineName);
1292
+ function resolveEngineToAdapter(engineName, adapters) {
1293
+ const exact = adapters.find((a) => a.id === engineName);
1257
1294
  if (exact) return exact.id;
1258
1295
  const needle = engineName.toLowerCase();
1259
- const matches = adapters2.filter((a) => {
1296
+ const matches = adapters.filter((a) => {
1260
1297
  return a.id.toLowerCase().includes(needle) || a.displayName.toLowerCase().includes(needle);
1261
1298
  });
1262
1299
  return matches.length === 1 ? matches[0].id : void 0;
1263
1300
  }
1264
- function applyCodacyYamlExcludes(config, yamlConfig, adapters2, logger) {
1301
+ function applyCodacyYamlExcludes(config, yamlConfig, adapters, logger) {
1265
1302
  const hasGlobalIncludes = yamlConfig.includePaths.length > 0;
1266
1303
  const hasEngineIncludes = Array.from(yamlConfig.engines.values()).some(
1267
1304
  (e) => e.includePaths.length > 0
@@ -1273,7 +1310,7 @@ function applyCodacyYamlExcludes(config, yamlConfig, adapters2, logger) {
1273
1310
  const tools = config.tools.map((tool) => ({ ...tool }));
1274
1311
  for (const [engineName, engineConfig] of yamlConfig.engines) {
1275
1312
  if (engineConfig.excludePaths.length === 0) continue;
1276
- const adapterId = resolveEngineToAdapter(engineName, adapters2);
1313
+ const adapterId = resolveEngineToAdapter(engineName, adapters);
1277
1314
  if (!adapterId) {
1278
1315
  logger?.warn(`Could not resolve .codacy.yaml engine "${engineName}" to a registered adapter`);
1279
1316
  continue;
@@ -4798,6 +4835,34 @@ async function listRepositoryToolPatterns(provider, org, repo, toolUuid) {
4798
4835
  );
4799
4836
  return all;
4800
4837
  }
4838
+ async function listIgnoredFiles(provider, org, repo) {
4839
+ const MAX_PAGES = 10;
4840
+ const files = [];
4841
+ let cursor;
4842
+ let pages = 0;
4843
+ while (pages < MAX_PAGES) {
4844
+ const page = await RepositoryService.listIgnoredFiles(
4845
+ provider,
4846
+ org,
4847
+ repo,
4848
+ void 0,
4849
+ // branch — use default branch
4850
+ void 0,
4851
+ // search
4852
+ cursor,
4853
+ 100
4854
+ );
4855
+ files.push(...page.data.map((f) => f.filepath));
4856
+ cursor = page.pagination?.cursor;
4857
+ pages++;
4858
+ if (!cursor || page.data.length === 0) break;
4859
+ }
4860
+ return {
4861
+ files,
4862
+ totalFetched: files.length,
4863
+ capped: pages >= MAX_PAGES && cursor !== void 0
4864
+ };
4865
+ }
4801
4866
 
4802
4867
  // src/init.ts
4803
4868
  async function detectLanguages(repositoryRoot) {
@@ -4816,7 +4881,7 @@ async function detectLanguages(repositoryRoot) {
4816
4881
  for (const file of files) {
4817
4882
  const basename = import_path6.default.basename(file);
4818
4883
  const ext = import_path6.default.extname(file).toLowerCase();
4819
- for (const [lang, config] of Object.entries(import_tooling.LANGUAGE_CONFIG)) {
4884
+ for (const [lang, config] of Object.entries(import_tooling5.LANGUAGE_CONFIG)) {
4820
4885
  if (config.extensions.includes(ext)) {
4821
4886
  detected.add(lang);
4822
4887
  } else if (config.files.includes(basename)) {
@@ -4826,10 +4891,10 @@ async function detectLanguages(repositoryRoot) {
4826
4891
  }
4827
4892
  return Array.from(detected).sort();
4828
4893
  }
4829
- async function detectLocalConfigFiles(repositoryRoot, adapters2) {
4894
+ async function detectLocalConfigFiles(repositoryRoot, adapters) {
4830
4895
  const found = /* @__PURE__ */ new Map();
4831
4896
  const checks = await Promise.allSettled(
4832
- adapters2.map(async (adapter) => {
4897
+ adapters.map(async (adapter) => {
4833
4898
  const result = await adapter.checkLocalConfigurationFile({
4834
4899
  repositoryRoot,
4835
4900
  generatedConfigDir: import_path6.default.join(repositoryRoot, ".codacy", "generated"),
@@ -4860,17 +4925,17 @@ var silentLogger = {
4860
4925
  error: () => {
4861
4926
  }
4862
4927
  };
4863
- async function enrichWithCodacyYaml(config, repositoryRoot, adapters2) {
4928
+ async function enrichWithCodacyYaml(config, repositoryRoot, adapters) {
4864
4929
  const yamlConfig = await readCodacyYaml(repositoryRoot);
4865
4930
  if (!yamlConfig) return { config, codacyYamlApplied: false };
4866
4931
  return {
4867
- config: applyCodacyYamlExcludes(config, yamlConfig, adapters2),
4932
+ config: applyCodacyYamlExcludes(config, yamlConfig, adapters),
4868
4933
  codacyYamlApplied: true
4869
4934
  };
4870
4935
  }
4871
- async function initLocalConfigBase(repositoryRoot, adapters2) {
4936
+ async function initLocalConfigBase(repositoryRoot, adapters) {
4872
4937
  const languages = await detectLanguages(repositoryRoot);
4873
- const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters2);
4938
+ const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
4874
4939
  const tools = [];
4875
4940
  for (const [toolId, configPath] of localConfigs.entries()) {
4876
4941
  tools.push({
@@ -4882,17 +4947,17 @@ async function initLocalConfigBase(repositoryRoot, adapters2) {
4882
4947
  }
4883
4948
  return createCodacyConfig({ source: "local", languages, tools });
4884
4949
  }
4885
- async function initLocalConfig(repositoryRoot, adapters2) {
4886
- const baseConfig = await initLocalConfigBase(repositoryRoot, adapters2);
4950
+ async function initLocalConfig(repositoryRoot, adapters) {
4951
+ const baseConfig = await initLocalConfigBase(repositoryRoot, adapters);
4887
4952
  const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
4888
4953
  baseConfig,
4889
4954
  repositoryRoot,
4890
- adapters2
4955
+ adapters
4891
4956
  );
4892
4957
  return { config, stats: { codacyYamlApplied } };
4893
4958
  }
4894
- async function initDefaultConfig(repositoryRoot, adapters2) {
4895
- const base = await initLocalConfigBase(repositoryRoot, adapters2);
4959
+ async function initDefaultConfig(repositoryRoot, adapters) {
4960
+ const base = await initLocalConfigBase(repositoryRoot, adapters);
4896
4961
  const toolsWithLocalConfig = new Set(
4897
4962
  base.tools.filter((t) => t.useLocalConfigurationFile).map((t) => t.toolId)
4898
4963
  );
@@ -4901,7 +4966,7 @@ async function initDefaultConfig(repositoryRoot, adapters2) {
4901
4966
  const updatedTools = [...base.tools];
4902
4967
  const coveredToolIds = new Set(updatedTools.map((t) => t.toolId));
4903
4968
  let addedTools = 0;
4904
- for (const adapter of adapters2) {
4969
+ for (const adapter of adapters) {
4905
4970
  if (toolsWithLocalConfig.has(adapter.id)) continue;
4906
4971
  const apiTool = apiTools.find(
4907
4972
  (t) => t.prefix ? t.prefix.replace(/_$/, "") === adapter.id : t.shortName === adapter.id
@@ -4935,20 +5000,26 @@ async function initDefaultConfig(repositoryRoot, adapters2) {
4935
5000
  const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
4936
5001
  baseConfig,
4937
5002
  repositoryRoot,
4938
- adapters2
5003
+ adapters
4939
5004
  );
4940
5005
  return { config, stats: { totalApiTools: apiTools.length, addedTools, codacyYamlApplied } };
4941
5006
  }
4942
- async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters2, apiToken, onRemoteToolsFound, onToolProcessed) {
5007
+ async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters, apiToken, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
4943
5008
  configureApiToken(apiToken);
4944
5009
  const repoInfo = await getRepository(provider, organization, repositoryName);
4945
5010
  const remoteTools = await listRepositoryTools(provider, organization, repositoryName);
5011
+ let ignoredFilesResult = { files: [], totalFetched: 0, capped: false };
5012
+ try {
5013
+ ignoredFilesResult = await listIgnoredFiles(provider, organization, repositoryName);
5014
+ } catch {
5015
+ }
5016
+ onIgnoredFilesFetched?.(ignoredFilesResult.totalFetched, ignoredFilesResult.capped);
4946
5017
  const adapterByName = /* @__PURE__ */ new Map();
4947
- for (const adapter of adapters2) {
5018
+ for (const adapter of adapters) {
4948
5019
  adapterByName.set(adapter.id, adapter);
4949
5020
  adapterByName.set(adapter.displayName.toLowerCase(), adapter);
4950
5021
  }
4951
- const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters2);
5022
+ const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
4952
5023
  const allApiTools = await listTools();
4953
5024
  const adapterByUuid = /* @__PURE__ */ new Map();
4954
5025
  for (const apiTool of allApiTools) {
@@ -5005,17 +5076,26 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
5005
5076
  repositoryId: repoInfo.repositoryId != null ? String(repoInfo.repositoryId) : void 0,
5006
5077
  repositoryName
5007
5078
  });
5079
+ if (ignoredFilesResult.files.length > 0) {
5080
+ baseConfig.exclude = [.../* @__PURE__ */ new Set([...baseConfig.exclude ?? [], ...ignoredFilesResult.files])];
5081
+ }
5008
5082
  const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
5009
5083
  baseConfig,
5010
5084
  repositoryRoot,
5011
- adapters2
5085
+ adapters
5012
5086
  );
5013
5087
  return {
5014
5088
  config,
5015
- stats: { totalRemoteTools: remoteTools.length, matchedAdapters, codacyYamlApplied }
5089
+ stats: {
5090
+ totalRemoteTools: remoteTools.length,
5091
+ matchedAdapters,
5092
+ codacyYamlApplied,
5093
+ ignoredFilesCount: ignoredFilesResult.totalFetched,
5094
+ ignoredFilesCapped: ignoredFilesResult.capped
5095
+ }
5016
5096
  };
5017
5097
  }
5018
- async function initContainerConfig(repositoryRoot, adapters2) {
5098
+ async function initContainerConfig(repositoryRoot, adapters) {
5019
5099
  const codacyrcPath = import_path6.default.join(repositoryRoot, ".codacyrc");
5020
5100
  let raw;
5021
5101
  try {
@@ -5035,7 +5115,7 @@ async function initContainerConfig(repositoryRoot, adapters2) {
5035
5115
  }
5036
5116
  }
5037
5117
  const includeFiles = Array.isArray(parsed.files) ? parsed.files.filter((f) => typeof f === "string") : [];
5038
- const adapterById = new Map(adapters2.map((a) => [a.id, a]));
5118
+ const adapterById = new Map(adapters.map((a) => [a.id, a]));
5039
5119
  const tools = [];
5040
5120
  if (Array.isArray(parsed.tools)) {
5041
5121
  for (const toolEntry of parsed.tools) {
@@ -5082,15 +5162,15 @@ var LEVEL_ORDER = {
5082
5162
  error: 3
5083
5163
  };
5084
5164
  function rotateLogFiles(logsDir, maxFiles) {
5085
- let entries;
5165
+ let entries2;
5086
5166
  try {
5087
- entries = import_fs.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
5167
+ entries2 = import_fs.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
5088
5168
  } catch {
5089
5169
  return;
5090
5170
  }
5091
- if (entries.length <= maxFiles) return;
5092
- entries.sort();
5093
- const toRemove = entries.slice(0, entries.length - maxFiles);
5171
+ if (entries2.length <= maxFiles) return;
5172
+ entries2.sort();
5173
+ const toRemove = entries2.slice(0, entries2.length - maxFiles);
5094
5174
  for (const f of toRemove) {
5095
5175
  try {
5096
5176
  import_fs.default.unlinkSync(import_path7.default.join(logsDir, f));
@@ -5188,17 +5268,21 @@ function createLogger(options = {}) {
5188
5268
  detectLanguages,
5189
5269
  formatOutput,
5190
5270
  formatTextSummary,
5271
+ getAdapter,
5191
5272
  getRegisteredAdapters,
5273
+ getRegisteredDescriptors,
5192
5274
  initContainerConfig,
5193
5275
  initDefaultConfig,
5194
5276
  initLocalConfig,
5195
5277
  initRemoteConfig,
5196
5278
  listDiffFiles,
5279
+ listIgnoredFiles,
5197
5280
  listPrFiles,
5198
5281
  listStagedFiles,
5199
5282
  readCodacyConfig,
5200
5283
  readCodacyYaml,
5201
5284
  registerAdapter,
5285
+ registerLazyAdapter,
5202
5286
  resolveEngineToAdapter,
5203
5287
  rotateLogFiles,
5204
5288
  validateApiToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.4.0",
3
+ "version": "0.6.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.4.0"
15
+ "@codacy/tooling": "0.6.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.4.0"
19
+ "@codacy/tools-dummy-1": "0.6.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",