@codacy/analysis-runner 0.11.0 → 0.12.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
@@ -27,7 +27,7 @@ interface AnalyzeOptions {
27
27
  outputFormat?: OutputFormat;
28
28
  /** Execution mode. Defaults to "standard". */
29
29
  mode?: ExecutionMode;
30
- /** Maximum time in ms for each tool invocation. Defaults to 600000 (10 minutes). */
30
+ /** Maximum time in ms for each tool invocation. Defaults to 900000 (15 minutes). */
31
31
  toolTimeout?: number;
32
32
  /** Maximum number of tools to run in parallel. 1 = sequential. Defaults to 1. */
33
33
  parallelTools?: number;
@@ -601,11 +601,10 @@ interface PatternMetadataLookup {
601
601
  declare function computePatternSummary(issues: readonly Issue[], lookup: ReadonlyMap<string, PatternMetadataLookup>): PatternSummary;
602
602
 
603
603
  /**
604
- * File discovery and routing — finds files in a repository, applies exclusion
605
- * layers (.gitignore, global exclude, per-tool exclude), and routes files to
606
- * the appropriate tool adapters based on file patterns.
604
+ * Git-scope file resolution — resolves the set of files implied by the
605
+ * `--staged`, `--diff`, and `--pr` flags. Each helper returns paths relative
606
+ * to the repository root, excluding deleted files, sorted for stable output.
607
607
  */
608
-
609
608
  /**
610
609
  * Returns files changed in the git staging area (index), excluding deleted files.
611
610
  * Used by `--staged` to analyze only what's about to be committed.
package/dist/index.js CHANGED
@@ -76,7 +76,7 @@ module.exports = __toCommonJS(index_exports);
76
76
  // src/analyze.ts
77
77
  var import_os = __toESM(require("os"));
78
78
  var import_path3 = __toESM(require("path"));
79
- var import_child_process2 = require("child_process");
79
+ var import_child_process3 = require("child_process");
80
80
  var import_util = require("util");
81
81
  var import_tooling3 = require("@codacy/tooling");
82
82
 
@@ -238,11 +238,13 @@ function createCodacyConfig(params = {}) {
238
238
  }
239
239
 
240
240
  // src/files.ts
241
- var import_child_process = require("child_process");
241
+ var import_child_process2 = require("child_process");
242
242
  var import_fs = __toESM(require("fs"));
243
243
  var import_path2 = __toESM(require("path"));
244
+ var import_minimatch2 = require("minimatch");
245
+
246
+ // src/glob-matcher.ts
244
247
  var import_minimatch = require("minimatch");
245
- var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
246
248
  var PURE_EXTENSION_PATTERN = /^\*\*\/\*(\.[^*/.]+)$/;
247
249
  function pureExtensionOf(pattern) {
248
250
  const m = PURE_EXTENSION_PATTERN.exec(pattern);
@@ -325,69 +327,9 @@ var CompiledMatcher = class {
325
327
  return false;
326
328
  }
327
329
  };
328
- function listRepoFiles(repositoryRoot) {
329
- try {
330
- const tracked = (0, import_child_process.execSync)("git ls-files", {
331
- cwd: repositoryRoot,
332
- encoding: "utf-8",
333
- maxBuffer: 50 * 1024 * 1024
334
- }).trim();
335
- const untracked = (0, import_child_process.execSync)("git ls-files --others --exclude-standard", {
336
- cwd: repositoryRoot,
337
- encoding: "utf-8",
338
- maxBuffer: 50 * 1024 * 1024
339
- }).trim();
340
- const files = /* @__PURE__ */ new Set();
341
- for (const f of tracked.split("\n")) {
342
- if (f) files.add(f);
343
- }
344
- for (const f of untracked.split("\n")) {
345
- if (f) files.add(f);
346
- }
347
- return Array.from(files).sort();
348
- } catch {
349
- return [];
350
- }
351
- }
352
- function filterOversizedFiles(files, repositoryRoot, logger) {
353
- const result = [];
354
- let skipped = 0;
355
- for (const f of files) {
356
- try {
357
- const stat = import_fs.default.statSync(import_path2.default.join(repositoryRoot, f));
358
- if (stat.size > MAX_FILE_SIZE_BYTES) {
359
- skipped++;
360
- logger?.debug("Skipping oversized file", {
361
- file: f,
362
- sizeKB: Math.round(stat.size / 1024),
363
- limitKB: MAX_FILE_SIZE_BYTES / 1024
364
- });
365
- continue;
366
- }
367
- } catch {
368
- }
369
- result.push(f);
370
- }
371
- if (skipped > 0) {
372
- logger?.info("Filtered oversized files", { skipped, remaining: result.length });
373
- }
374
- return result;
375
- }
376
- function applyExclusions(files, globalExclude, toolExclude) {
377
- if (globalExclude.length === 0 && toolExclude.length === 0) return files;
378
- const matcher = new CompiledMatcher([...globalExclude, ...toolExclude]);
379
- if (matcher.isEmpty) return files;
380
- return files.filter((file) => !matcher.matches(file, import_path2.default.basename(file)));
381
- }
382
- function routeFilesToTool(files, adapter, toolConfig) {
383
- const patterns = [...adapter.filePatterns];
384
- if (toolConfig?.extraFilePatterns) {
385
- patterns.push(...toolConfig.extraFilePatterns);
386
- }
387
- const matcher = new CompiledMatcher(patterns);
388
- if (matcher.matchAll) return files;
389
- return files.filter((file) => matcher.matches(file, import_path2.default.basename(file)));
390
- }
330
+
331
+ // src/git-scope.ts
332
+ var import_child_process = require("child_process");
391
333
  function listStagedFiles(repositoryRoot) {
392
334
  try {
393
335
  const output = (0, import_child_process.execSync)("git diff --cached --name-only --diff-filter=d", {
@@ -417,7 +359,7 @@ function listDiffFiles(repositoryRoot, base) {
417
359
  }
418
360
  function listPrFiles(repositoryRoot) {
419
361
  try {
420
- const output = (0, import_child_process.execSync)("gh pr view --json files --jq '.files[].path'", {
362
+ const output = (0, import_child_process.execSync)('gh pr view --json files --jq ".files[].path"', {
421
363
  cwd: repositoryRoot,
422
364
  encoding: "utf-8",
423
365
  maxBuffer: 10 * 1024 * 1024,
@@ -473,11 +415,81 @@ function detectDefaultBranch(repositoryRoot) {
473
415
  "Cannot auto-detect default branch: no origin/HEAD, origin/main, or origin/master found. Specify a base explicitly with --diff <base>."
474
416
  );
475
417
  }
418
+
419
+ // src/files.ts
420
+ var MAX_FILE_SIZE_BYTES = 150 * 1024;
421
+ function listRepoFiles(repositoryRoot) {
422
+ try {
423
+ const tracked = (0, import_child_process2.execSync)("git ls-files", {
424
+ cwd: repositoryRoot,
425
+ encoding: "utf-8",
426
+ maxBuffer: 50 * 1024 * 1024
427
+ }).trim();
428
+ const untracked = (0, import_child_process2.execSync)("git ls-files --others --exclude-standard", {
429
+ cwd: repositoryRoot,
430
+ encoding: "utf-8",
431
+ maxBuffer: 50 * 1024 * 1024
432
+ }).trim();
433
+ const files = /* @__PURE__ */ new Set();
434
+ for (const f of tracked.split("\n")) {
435
+ if (f) files.add(f);
436
+ }
437
+ for (const f of untracked.split("\n")) {
438
+ if (f) files.add(f);
439
+ }
440
+ return Array.from(files).sort();
441
+ } catch {
442
+ return [];
443
+ }
444
+ }
445
+ function filterOversizedFiles(files, repositoryRoot, logger) {
446
+ const result = [];
447
+ const skipped = [];
448
+ for (const f of files) {
449
+ try {
450
+ const stat = import_fs.default.statSync(import_path2.default.join(repositoryRoot, f));
451
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
452
+ skipped.push({ filePath: f, sizeBytes: stat.size });
453
+ logger?.debug("Skipping oversized file", {
454
+ file: f,
455
+ sizeKB: Math.round(stat.size / 1024),
456
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
457
+ });
458
+ continue;
459
+ }
460
+ } catch {
461
+ }
462
+ result.push(f);
463
+ }
464
+ if (skipped.length > 0) {
465
+ logger?.info("Filtered oversized files", {
466
+ skipped: skipped.length,
467
+ remaining: result.length,
468
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
469
+ });
470
+ }
471
+ return { files: result, skipped };
472
+ }
473
+ function applyExclusions(files, globalExclude, toolExclude) {
474
+ if (globalExclude.length === 0 && toolExclude.length === 0) return files;
475
+ const matcher = new CompiledMatcher([...globalExclude, ...toolExclude]);
476
+ if (matcher.isEmpty) return files;
477
+ return files.filter((file) => !matcher.matches(file, import_path2.default.basename(file)));
478
+ }
479
+ function routeFilesToTool(files, adapter, toolConfig) {
480
+ const patterns = [...adapter.filePatterns];
481
+ if (toolConfig?.extraFilePatterns) {
482
+ patterns.push(...toolConfig.extraFilePatterns);
483
+ }
484
+ const matcher = new CompiledMatcher(patterns);
485
+ if (matcher.matchAll) return files;
486
+ return files.filter((file) => matcher.matches(file, import_path2.default.basename(file)));
487
+ }
476
488
  function filterByTargetFiles(allFiles, targetPatterns) {
477
489
  if (targetPatterns.length === 0) return allFiles;
478
490
  const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
479
491
  const exact = new Set(normalized);
480
- const globs = normalized.map((p) => new import_minimatch.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
492
+ const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
481
493
  const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
482
494
  return allFiles.filter(
483
495
  (f) => (
@@ -491,6 +503,7 @@ function filterByTargetFiles(allFiles, targetPatterns) {
491
503
 
492
504
  // src/executor.ts
493
505
  var import_tooling2 = require("@codacy/tooling");
506
+ var HARD_TIMEOUT_GRACE_MS = 2e3;
494
507
  async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
495
508
  const startTime = Date.now();
496
509
  ctx.logger.debug("runTool started", {
@@ -498,12 +511,18 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
498
511
  fileCount: ctx.targetFiles.length,
499
512
  timeoutMs
500
513
  });
514
+ const abortController = new AbortController();
515
+ ctx.signal = abortController.signal;
516
+ const abortTimer = setTimeout(() => abortController.abort(), timeoutMs);
517
+ abortTimer.unref?.();
518
+ let hardTimer;
501
519
  try {
502
520
  const timeoutPromise = new Promise((_resolve, reject) => {
503
- setTimeout(
521
+ hardTimer = setTimeout(
504
522
  () => reject(new Error(`TIMEOUT: Tool ${adapter.id} exceeded ${(0, import_tooling2.formatDuration)(timeoutMs)}`)),
505
- timeoutMs
523
+ timeoutMs + HARD_TIMEOUT_GRACE_MS
506
524
  );
525
+ hardTimer.unref?.();
507
526
  });
508
527
  const result = await Promise.race([adapter.analyze(ctx, codacyToolConfig), timeoutPromise]);
509
528
  ctx.logger.debug("runTool completed", {
@@ -550,6 +569,9 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
550
569
  filesAnalyzed: ctx.targetFiles.length
551
570
  }
552
571
  };
572
+ } finally {
573
+ clearTimeout(abortTimer);
574
+ if (hardTimer) clearTimeout(hardTimer);
553
575
  }
554
576
  }
555
577
  async function runToolsSequential(tools, timeoutMs, callbacks) {
@@ -708,7 +730,7 @@ function computePatternSummary(issues, lookup) {
708
730
  }
709
731
 
710
732
  // src/analyze.ts
711
- var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
733
+ var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
712
734
  var nullLogger = {
713
735
  debug: () => {
714
736
  },
@@ -736,7 +758,7 @@ async function mapWithConcurrency(items, concurrency, task) {
736
758
  async function analyze(options) {
737
759
  const startedAt = /* @__PURE__ */ new Date();
738
760
  const mode = options.mode || "standard";
739
- const toolTimeout = options.toolTimeout || 6e5;
761
+ const toolTimeout = options.toolTimeout || 9e5;
740
762
  const parallelTools = options.parallelTools || 1;
741
763
  const logger = options.logger ?? nullLogger;
742
764
  const phaseTimings = {
@@ -1096,7 +1118,9 @@ async function analyze(options) {
1096
1118
  allFiles = filterByTargetFiles(allFiles, options.files);
1097
1119
  logger.debug("Filtered by target files", { afterFilter: allFiles.length });
1098
1120
  }
1099
- allFiles = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
1121
+ const oversize = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
1122
+ allFiles = oversize.files;
1123
+ const skippedFiles = oversize.skipped;
1100
1124
  phaseTimings.fileDiscoveryMs = Date.now() - fileDiscoveryStartMs;
1101
1125
  const fileRoutingStartMs = Date.now();
1102
1126
  const readyById = new Map(capability.ready.map((r) => [r.toolId, r]));
@@ -1213,7 +1237,8 @@ async function analyze(options) {
1213
1237
  allErrors,
1214
1238
  allSummaries,
1215
1239
  patternSummary,
1216
- phaseTimings
1240
+ phaseTimings,
1241
+ skippedFiles
1217
1242
  );
1218
1243
  }
1219
1244
  function makeToolContext(base, adapter, targetFiles) {
@@ -1239,7 +1264,7 @@ function resolveGitScope(repositoryRoot, scope) {
1239
1264
  const base = scope.diff === true ? void 0 : scope.diff;
1240
1265
  return listDiffFiles(repositoryRoot, base);
1241
1266
  }
1242
- function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings) {
1267
+ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings, skippedFiles) {
1243
1268
  const completedAt = /* @__PURE__ */ new Date();
1244
1269
  return {
1245
1270
  metadata: {
@@ -1254,7 +1279,8 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
1254
1279
  issues,
1255
1280
  errors,
1256
1281
  toolResults,
1257
- ...patternSummary ? { patternSummary } : {}
1282
+ ...patternSummary ? { patternSummary } : {},
1283
+ ...skippedFiles && skippedFiles.length > 0 ? { skippedFiles } : {}
1258
1284
  };
1259
1285
  }
1260
1286
 
@@ -1263,6 +1289,7 @@ var import_path4 = __toESM(require("path"));
1263
1289
  var import_tooling4 = require("@codacy/tooling");
1264
1290
  var import_ansis = __toESM(require("ansis"));
1265
1291
  var import_cli_table3 = __toESM(require("cli-table3"));
1292
+ var MAX_SKIPPED_FILES_SHOWN = 5;
1266
1293
  function formatOutput(result, format) {
1267
1294
  switch (format) {
1268
1295
  case "json":
@@ -1420,9 +1447,30 @@ function formatText(result) {
1420
1447
  lines.push(...formatErrorSection(result));
1421
1448
  lines.push("");
1422
1449
  }
1450
+ if (result.skippedFiles && result.skippedFiles.length > 0) {
1451
+ lines.push(...formatSkippedFilesSection(result.skippedFiles));
1452
+ lines.push("");
1453
+ }
1423
1454
  lines.push(formatTextSummary(result, isInspect));
1424
1455
  return lines.join("\n");
1425
1456
  }
1457
+ function formatSkippedFilesSection(skipped) {
1458
+ const limitKB = Math.round(MAX_FILE_SIZE_BYTES / 1024);
1459
+ const out = [""];
1460
+ out.push(
1461
+ import_ansis.default.yellow(
1462
+ `\u26A0 ${skipped.length} file${skipped.length !== 1 ? "s" : ""} skipped (exceeding the ${limitKB} KB size limit):`
1463
+ )
1464
+ );
1465
+ for (const f of skipped.slice(0, MAX_SKIPPED_FILES_SHOWN)) {
1466
+ out.push(import_ansis.default.dim(` - ${f.filePath} (${Math.round(f.sizeBytes / 1024)} KB)`));
1467
+ }
1468
+ const remaining = skipped.length - MAX_SKIPPED_FILES_SHOWN;
1469
+ if (remaining > 0) {
1470
+ out.push(import_ansis.default.dim(` \u2026 and ${remaining} more`));
1471
+ }
1472
+ return out;
1473
+ }
1426
1474
  function formatTextSummary(result, isInspect) {
1427
1475
  const lines = [];
1428
1476
  const inspect = isInspect ?? result.metadata.executionMode === "inspect";
@@ -1564,6 +1612,13 @@ function formatTextSummary(result, isInspect) {
1564
1612
  return lines.join("\n");
1565
1613
  }
1566
1614
  var MAX_ERRORS_PER_GROUP = 5;
1615
+ var MAX_ERROR_MESSAGE_LENGTH = 240;
1616
+ function flattenErrorMessage(message) {
1617
+ return message.replace(/\s+/g, " ").trim();
1618
+ }
1619
+ function truncateErrorMessage(message) {
1620
+ return message.length > MAX_ERROR_MESSAGE_LENGTH ? `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}\u2026` : message;
1621
+ }
1567
1622
  function formatErrorSection(result) {
1568
1623
  const lines = [];
1569
1624
  const displayNameMap = /* @__PURE__ */ new Map();
@@ -1585,13 +1640,14 @@ function formatErrorSection(result) {
1585
1640
  const deduped = [];
1586
1641
  const seen = /* @__PURE__ */ new Map();
1587
1642
  for (const err of groupErrors) {
1588
- const dedupKey = `${err.filePath ?? ""}|||${err.message}`;
1643
+ const flat = flattenErrorMessage(err.message);
1644
+ const dedupKey = `${err.filePath ?? ""}|||${flat}`;
1589
1645
  const idx = seen.get(dedupKey);
1590
1646
  if (idx !== void 0) {
1591
1647
  deduped[idx].count++;
1592
1648
  } else {
1593
1649
  seen.set(dedupKey, deduped.length);
1594
- deduped.push({ filePath: err.filePath, message: err.message, count: 1 });
1650
+ deduped.push({ filePath: err.filePath, message: truncateErrorMessage(flat), count: 1 });
1595
1651
  }
1596
1652
  }
1597
1653
  const shown = deduped.slice(0, MAX_ERRORS_PER_GROUP);
@@ -1918,7 +1974,7 @@ function applyConfigOperation(op, source, dest) {
1918
1974
  // src/init.ts
1919
1975
  var import_promises4 = __toESM(require("fs/promises"));
1920
1976
  var import_path7 = __toESM(require("path"));
1921
- var import_child_process3 = require("child_process");
1977
+ var import_child_process4 = require("child_process");
1922
1978
  var import_tooling5 = require("@codacy/tooling");
1923
1979
 
1924
1980
  // src/discover.ts
@@ -6422,7 +6478,7 @@ async function detectLanguages(repositoryRoot, precomputedFiles) {
6422
6478
  files = precomputedFiles;
6423
6479
  } else {
6424
6480
  try {
6425
- const output = (0, import_child_process3.execSync)("git ls-files", {
6481
+ const output = (0, import_child_process4.execSync)("git ls-files", {
6426
6482
  cwd: repositoryRoot,
6427
6483
  encoding: "utf-8",
6428
6484
  maxBuffer: 50 * 1024 * 1024
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/analysis-runner",
3
- "version": "0.11.0",
3
+ "version": "0.12.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.11.0"
15
+ "@codacy/tooling": "0.12.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@codacy/openapi-typescript-codegen": "0.0.9",
19
- "@codacy/tools-dummy-1": "0.11.0"
19
+ "@codacy/tools-dummy-1": "0.12.0"
20
20
  },
21
21
  "license": "MIT",
22
22
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",