@codacy/analysis-runner 0.8.0 → 0.8.1

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
@@ -52,6 +52,8 @@ interface AnalyzeOptions {
52
52
  onToolStart?: (toolId: string, displayName: string) => void;
53
53
  /** Called when a tool finishes executing. */
54
54
  onToolComplete?: (toolId: string, displayName: string, issueCount: number, durationMs: number) => void;
55
+ /** Called when a tool reports mid-execution progress (e.g. "Linting 50/200 files"). */
56
+ onToolProgress?: (toolId: string, displayName: string, message: string) => void;
55
57
  }
56
58
  /**
57
59
  * Runs analysis on the given repository and returns the unified result.
package/dist/index.js CHANGED
@@ -214,8 +214,10 @@ function createCodacyConfig(params = {}) {
214
214
 
215
215
  // src/files.ts
216
216
  var import_child_process = require("child_process");
217
+ var import_fs = __toESM(require("fs"));
217
218
  var import_path2 = __toESM(require("path"));
218
219
  var import_minimatch = require("minimatch");
220
+ var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
219
221
  function listRepoFiles(repositoryRoot) {
220
222
  try {
221
223
  const tracked = (0, import_child_process.execSync)("git ls-files", {
@@ -240,6 +242,30 @@ function listRepoFiles(repositoryRoot) {
240
242
  return [];
241
243
  }
242
244
  }
245
+ function filterOversizedFiles(files, repositoryRoot, logger) {
246
+ const result = [];
247
+ let skipped = 0;
248
+ for (const f of files) {
249
+ try {
250
+ const stat = import_fs.default.statSync(import_path2.default.join(repositoryRoot, f));
251
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
252
+ skipped++;
253
+ logger?.debug("Skipping oversized file", {
254
+ file: f,
255
+ sizeKB: Math.round(stat.size / 1024),
256
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
257
+ });
258
+ continue;
259
+ }
260
+ } catch {
261
+ }
262
+ result.push(f);
263
+ }
264
+ if (skipped > 0) {
265
+ logger?.info("Filtered oversized files", { skipped, remaining: result.length });
266
+ }
267
+ return result;
268
+ }
243
269
  function applyExclusions(files, globalExclude, toolExclude) {
244
270
  const allExclusions = [...globalExclude, ...toolExclude];
245
271
  if (allExclusions.length === 0) return files;
@@ -431,6 +457,10 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
431
457
  async function runToolsSequential(tools, timeoutMs, callbacks) {
432
458
  const results = [];
433
459
  for (const { adapter, ctx, codacyToolConfig } of tools) {
460
+ if (callbacks?.onToolProgress) {
461
+ const cb = callbacks.onToolProgress;
462
+ ctx.onProgress = (message) => cb(adapter.id, adapter.displayName, message);
463
+ }
434
464
  callbacks?.onToolStart?.(adapter.id, adapter.displayName);
435
465
  const result = await runTool(adapter, ctx, codacyToolConfig, timeoutMs);
436
466
  callbacks?.onToolComplete?.(
@@ -450,6 +480,10 @@ async function runToolsParallel(tools, timeoutMs, concurrency, callbacks) {
450
480
  while (nextIndex < tools.length) {
451
481
  const index = nextIndex++;
452
482
  const { adapter, ctx, codacyToolConfig } = tools[index];
483
+ if (callbacks?.onToolProgress) {
484
+ const cb = callbacks.onToolProgress;
485
+ ctx.onProgress = (message) => cb(adapter.id, adapter.displayName, message);
486
+ }
453
487
  callbacks?.onToolStart?.(adapter.id, adapter.displayName);
454
488
  results[index] = await runTool(adapter, ctx, codacyToolConfig, timeoutMs);
455
489
  callbacks?.onToolComplete?.(
@@ -593,6 +627,13 @@ async function analyze(options) {
593
627
  const toolTimeout = options.toolTimeout || 6e5;
594
628
  const parallelTools = options.parallelTools || 1;
595
629
  const logger = options.logger ?? nullLogger;
630
+ const phaseTimings = {
631
+ availabilityMs: 0,
632
+ fileDiscoveryMs: 0,
633
+ fileRoutingMs: 0,
634
+ toolExecutionMs: 0,
635
+ installMs: 0
636
+ };
596
637
  const emptyCapability = {
597
638
  ready: [],
598
639
  unavailable: [],
@@ -671,6 +712,7 @@ async function analyze(options) {
671
712
  const ready = [];
672
713
  const unavailable = [];
673
714
  const availabilityTimeout = 6e4;
715
+ const availabilityStartMs = Date.now();
674
716
  for (const adapter of adapters) {
675
717
  const ctx = makeToolContext(baseCtx, adapter, []);
676
718
  const checkStart = Date.now();
@@ -729,6 +771,7 @@ async function analyze(options) {
729
771
  });
730
772
  }
731
773
  }
774
+ phaseTimings.availabilityMs = Date.now() - availabilityStartMs;
732
775
  const capability = {
733
776
  ready,
734
777
  unavailable,
@@ -744,6 +787,7 @@ async function analyze(options) {
744
787
  configuredToolCount: configuredIds.size
745
788
  });
746
789
  if (mode === "auto-install" && capability.unavailable.length > 0) {
790
+ const installStartMs = Date.now();
747
791
  const installErrors = [];
748
792
  for (let i = capability.unavailable.length - 1; i >= 0; i--) {
749
793
  const status = capability.unavailable[i];
@@ -807,6 +851,7 @@ async function analyze(options) {
807
851
  });
808
852
  }
809
853
  }
854
+ phaseTimings.installMs = Date.now() - installStartMs;
810
855
  if (installErrors.length > 0) {
811
856
  return buildResult(
812
857
  startedAt,
@@ -815,7 +860,9 @@ async function analyze(options) {
815
860
  capability,
816
861
  [],
817
862
  installErrors,
818
- []
863
+ [],
864
+ void 0,
865
+ phaseTimings
819
866
  );
820
867
  }
821
868
  }
@@ -867,6 +914,7 @@ async function analyze(options) {
867
914
  []
868
915
  );
869
916
  }
917
+ const fileDiscoveryStartMs = Date.now();
870
918
  const globalExclude = codacyConfig.exclude || [];
871
919
  let allFiles = listRepoFiles(options.repositoryRoot);
872
920
  logger.debug("File discovery", { totalFiles: allFiles.length });
@@ -888,6 +936,9 @@ async function analyze(options) {
888
936
  allFiles = filterByTargetFiles(allFiles, options.files);
889
937
  logger.debug("Filtered by target files", { afterFilter: allFiles.length });
890
938
  }
939
+ allFiles = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
940
+ phaseTimings.fileDiscoveryMs = Date.now() - fileDiscoveryStartMs;
941
+ const fileRoutingStartMs = Date.now();
891
942
  const readyAdapters = adapters.filter((a) => capability.ready.some((r) => r.toolId === a.id));
892
943
  const toolRuns = [];
893
944
  for (const adapter of readyAdapters) {
@@ -928,6 +979,8 @@ async function analyze(options) {
928
979
  });
929
980
  toolRuns.push({ adapter, ctx: configCtx, codacyToolConfig });
930
981
  }
982
+ phaseTimings.fileRoutingMs = Date.now() - fileRoutingStartMs;
983
+ const toolExecStartMs = Date.now();
931
984
  logger.info("Starting tool execution", {
932
985
  toolCount: toolRuns.length,
933
986
  parallel: parallelTools > 1,
@@ -935,7 +988,8 @@ async function analyze(options) {
935
988
  });
936
989
  const callbacks = {
937
990
  onToolStart: options.onToolStart,
938
- onToolComplete: options.onToolComplete
991
+ onToolComplete: options.onToolComplete,
992
+ onToolProgress: options.onToolProgress
939
993
  };
940
994
  let results;
941
995
  if (parallelTools > 1 && toolRuns.length > 1) {
@@ -943,6 +997,7 @@ async function analyze(options) {
943
997
  } else {
944
998
  results = await runToolsSequential(toolRuns, toolTimeout, callbacks);
945
999
  }
1000
+ phaseTimings.toolExecutionMs = Date.now() - toolExecStartMs;
946
1001
  const allIssues = normalizeIssues(results.flatMap((r) => r.issues));
947
1002
  const allErrors = results.flatMap((r) => r.errors);
948
1003
  const allSummaries = results.map((r) => r.summary);
@@ -961,7 +1016,8 @@ async function analyze(options) {
961
1016
  logger.info("Pipeline complete", {
962
1017
  totalIssues: allIssues.length,
963
1018
  totalErrors: allErrors.length,
964
- durationMs: Date.now() - startedAt.getTime()
1019
+ durationMs: Date.now() - startedAt.getTime(),
1020
+ phaseTimings
965
1021
  });
966
1022
  return buildResult(
967
1023
  startedAt,
@@ -971,7 +1027,8 @@ async function analyze(options) {
971
1027
  allIssues,
972
1028
  allErrors,
973
1029
  allSummaries,
974
- patternSummary
1030
+ patternSummary,
1031
+ phaseTimings
975
1032
  );
976
1033
  }
977
1034
  function makeToolContext(base, adapter, targetFiles) {
@@ -997,7 +1054,7 @@ function resolveGitScope(repositoryRoot, scope) {
997
1054
  const base = scope.diff === true ? void 0 : scope.diff;
998
1055
  return listDiffFiles(repositoryRoot, base);
999
1056
  }
1000
- function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary) {
1057
+ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings) {
1001
1058
  const completedAt = /* @__PURE__ */ new Date();
1002
1059
  return {
1003
1060
  metadata: {
@@ -1005,7 +1062,8 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
1005
1062
  completedAt: completedAt.toISOString(),
1006
1063
  durationMs: completedAt.getTime() - startedAt.getTime(),
1007
1064
  repositoryRoot,
1008
- executionMode: mode
1065
+ executionMode: mode,
1066
+ ...phaseTimings ? { phaseTimings } : {}
1009
1067
  },
1010
1068
  capability,
1011
1069
  issues,
@@ -1174,11 +1232,7 @@ function formatText(result) {
1174
1232
  if (result.errors.length > 0) {
1175
1233
  lines.push("");
1176
1234
  lines.push("Errors:");
1177
- for (const error of result.errors) {
1178
- const prefix = `[${error.toolId}/${error.phase}]`;
1179
- const location = error.filePath ? ` ${error.filePath}:` : "";
1180
- lines.push(import_ansis.default.red(` ${prefix}${location} ${error.message}`));
1181
- }
1235
+ lines.push(...formatErrorSection(result));
1182
1236
  lines.push("");
1183
1237
  }
1184
1238
  lines.push(formatTextSummary(result, isInspect));
@@ -1277,6 +1331,33 @@ function formatTextSummary(result, isInspect) {
1277
1331
  lines.push(table.toString());
1278
1332
  }
1279
1333
  }
1334
+ if (!inspect && result.metadata.phaseTimings) {
1335
+ const pt = result.metadata.phaseTimings;
1336
+ const totalMs = result.metadata.durationMs;
1337
+ const toolSumMs = pt.toolExecutionMs;
1338
+ const overheadMs = totalMs - toolSumMs;
1339
+ if (overheadMs > 2e3 && overheadMs / totalMs > 0.2) {
1340
+ lines.push("");
1341
+ lines.push(import_ansis.default.bold("Pipeline timing:"));
1342
+ const entries2 = [
1343
+ ["Availability checks", pt.availabilityMs],
1344
+ ["File discovery", pt.fileDiscoveryMs],
1345
+ ["File routing & config", pt.fileRoutingMs]
1346
+ ];
1347
+ if (pt.installMs > 0) {
1348
+ entries2.push(["Install", pt.installMs]);
1349
+ }
1350
+ entries2.push(["Tool execution", pt.toolExecutionMs]);
1351
+ const accounted = entries2.reduce((sum, [, ms]) => sum + ms, 0);
1352
+ const other = totalMs - accounted;
1353
+ if (other > 500) {
1354
+ entries2.push(["Other", other]);
1355
+ }
1356
+ for (const [label, ms] of entries2) {
1357
+ lines.push(` ${label.padEnd(22)} ${(0, import_tooling4.formatDuration)(ms)}`);
1358
+ }
1359
+ }
1360
+ }
1280
1361
  const toolsWithHints = result.capability.unavailable.filter((t) => t.remediation);
1281
1362
  if (toolsWithHints.length > 0) {
1282
1363
  lines.push("");
@@ -1295,6 +1376,51 @@ function formatTextSummary(result, isInspect) {
1295
1376
  }
1296
1377
  return lines.join("\n");
1297
1378
  }
1379
+ var MAX_ERRORS_PER_GROUP = 5;
1380
+ function formatErrorSection(result) {
1381
+ const lines = [];
1382
+ const displayNameMap = /* @__PURE__ */ new Map();
1383
+ for (const t of result.capability.ready) {
1384
+ displayNameMap.set(t.toolId, t.displayName ?? t.toolId);
1385
+ }
1386
+ for (const t of result.capability.unavailable) {
1387
+ displayNameMap.set(t.toolId, t.displayName ?? t.toolId);
1388
+ }
1389
+ const displayName = (toolId) => displayNameMap.get(toolId) ?? toolId;
1390
+ const groups = /* @__PURE__ */ new Map();
1391
+ for (const error of result.errors) {
1392
+ const key = `${displayName(error.toolId)}/${error.phase}`;
1393
+ const list = groups.get(key) ?? [];
1394
+ list.push(error);
1395
+ groups.set(key, list);
1396
+ }
1397
+ for (const [groupKey, groupErrors] of groups) {
1398
+ const deduped = [];
1399
+ const seen = /* @__PURE__ */ new Map();
1400
+ for (const err of groupErrors) {
1401
+ const dedupKey = `${err.filePath ?? ""}|||${err.message}`;
1402
+ const idx = seen.get(dedupKey);
1403
+ if (idx !== void 0) {
1404
+ deduped[idx].count++;
1405
+ } else {
1406
+ seen.set(dedupKey, deduped.length);
1407
+ deduped.push({ filePath: err.filePath, message: err.message, count: 1 });
1408
+ }
1409
+ }
1410
+ const shown = deduped.slice(0, MAX_ERRORS_PER_GROUP);
1411
+ const overflowCount = deduped.length - shown.length;
1412
+ for (const entry of shown) {
1413
+ const prefix = `[${groupKey}]`;
1414
+ const location = entry.filePath ? ` ${entry.filePath}:` : "";
1415
+ const suffix = entry.count > 1 ? ` (x${entry.count})` : "";
1416
+ lines.push(import_ansis.default.red(` ${prefix}${location} ${entry.message}${suffix}`));
1417
+ }
1418
+ if (overflowCount > 0) {
1419
+ lines.push(import_ansis.default.dim(` ... and ${overflowCount} more ${groupKey} errors`));
1420
+ }
1421
+ }
1422
+ return lines;
1423
+ }
1298
1424
  var MAX_LINE_LENGTH = 100;
1299
1425
  var MAX_MULTILINE_DISPLAY = 10;
1300
1426
  function formatMultilineIssue(output, issue) {
@@ -6315,7 +6441,7 @@ function parseAutoFilters(raw) {
6315
6441
  }
6316
6442
 
6317
6443
  // src/logger.ts
6318
- var import_fs = __toESM(require("fs"));
6444
+ var import_fs2 = __toESM(require("fs"));
6319
6445
  var import_path8 = __toESM(require("path"));
6320
6446
  var import_os2 = __toESM(require("os"));
6321
6447
  var LEVEL_ORDER = {
@@ -6327,7 +6453,7 @@ var LEVEL_ORDER = {
6327
6453
  function rotateLogFiles(logsDir, maxFiles) {
6328
6454
  let entries2;
6329
6455
  try {
6330
- entries2 = import_fs.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
6456
+ entries2 = import_fs2.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
6331
6457
  } catch {
6332
6458
  return;
6333
6459
  }
@@ -6336,7 +6462,7 @@ function rotateLogFiles(logsDir, maxFiles) {
6336
6462
  const toRemove = entries2.slice(0, entries2.length - maxFiles);
6337
6463
  for (const f of toRemove) {
6338
6464
  try {
6339
- import_fs.default.unlinkSync(import_path8.default.join(logsDir, f));
6465
+ import_fs2.default.unlinkSync(import_path8.default.join(logsDir, f));
6340
6466
  } catch {
6341
6467
  }
6342
6468
  }
@@ -6378,10 +6504,10 @@ function createLogger(options = {}) {
6378
6504
  let logFd = null;
6379
6505
  let bytesWritten = 0;
6380
6506
  if (fileEnabled) {
6381
- import_fs.default.mkdirSync(logsDir, { recursive: true });
6507
+ import_fs2.default.mkdirSync(logsDir, { recursive: true });
6382
6508
  rotateLogFiles(logsDir, maxFiles);
6383
6509
  const logFilePath = import_path8.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
6384
- logFd = import_fs.default.openSync(logFilePath, "a");
6510
+ logFd = import_fs2.default.openSync(logFilePath, "a");
6385
6511
  }
6386
6512
  function log(level, message, fields) {
6387
6513
  const numLevel = LEVEL_ORDER[level];
@@ -6394,13 +6520,13 @@ function createLogger(options = {}) {
6394
6520
  ...fields
6395
6521
  });
6396
6522
  const buf = Buffer.from(entry + "\n", "utf-8");
6397
- import_fs.default.writeSync(logFd, buf);
6523
+ import_fs2.default.writeSync(logFd, buf);
6398
6524
  bytesWritten += buf.length;
6399
6525
  if (bytesWritten >= maxFileSize) {
6400
- import_fs.default.closeSync(logFd);
6526
+ import_fs2.default.closeSync(logFd);
6401
6527
  rotateLogFiles(logsDir, maxFiles);
6402
6528
  const newPath = import_path8.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
6403
- logFd = import_fs.default.openSync(newPath, "a");
6529
+ logFd = import_fs2.default.openSync(newPath, "a");
6404
6530
  bytesWritten = 0;
6405
6531
  }
6406
6532
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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.8.0"
15
+ "@codacy/tooling": "0.8.1"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.8.0"
19
+ "@codacy/tools-dummy-1": "0.8.1"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",