@codacy/analysis-runner 0.11.0 → 0.13.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.js CHANGED
@@ -39,12 +39,15 @@ __export(index_exports, {
39
39
  configureApiToken: () => configureApiToken,
40
40
  createCodacyConfig: () => createCodacyConfig,
41
41
  createLogger: () => createLogger,
42
+ createNullLogger: () => createNullLogger,
43
+ deleteCredentials: () => deleteCredentials,
42
44
  detectLanguages: () => detectLanguages,
43
45
  diffConfigs: () => diffConfigs,
44
46
  discover: () => discover,
45
47
  formatOutput: () => formatOutput,
46
48
  formatTextSummary: () => formatTextSummary,
47
49
  getAdapter: () => getAdapter,
50
+ getKnownDisplayNames: () => getKnownDisplayNames,
48
51
  getRegisteredAdapters: () => getRegisteredAdapters,
49
52
  getRegisteredDescriptors: () => getRegisteredDescriptors,
50
53
  initAutoConfig: () => initAutoConfig,
@@ -57,6 +60,7 @@ __export(index_exports, {
57
60
  listIgnoredFiles: () => listIgnoredFiles,
58
61
  listPrFiles: () => listPrFiles,
59
62
  listStagedFiles: () => listStagedFiles,
63
+ loadCredentials: () => loadCredentials,
60
64
  mergeConfigs: () => mergeConfigs,
61
65
  parseAutoFilters: () => parseAutoFilters,
62
66
  readCodacyConfig: () => readCodacyConfig,
@@ -64,9 +68,11 @@ __export(index_exports, {
64
68
  registerAdapter: () => registerAdapter,
65
69
  registerLazyAdapter: () => registerLazyAdapter,
66
70
  registerUnsupportedTool: () => registerUnsupportedTool,
71
+ resolveApiToken: () => resolveApiToken,
67
72
  resolveConfigPath: () => resolveConfigPath,
68
73
  resolveEngineToAdapter: () => resolveEngineToAdapter,
69
74
  rotateLogFiles: () => rotateLogFiles,
75
+ saveCredentials: () => saveCredentials,
70
76
  validateApiToken: () => validateApiToken,
71
77
  writeCodacyConfig: () => writeCodacyConfig,
72
78
  writeCodacyConfigFile: () => writeCodacyConfigFile
@@ -74,9 +80,9 @@ __export(index_exports, {
74
80
  module.exports = __toCommonJS(index_exports);
75
81
 
76
82
  // src/analyze.ts
77
- var import_os = __toESM(require("os"));
78
- var import_path3 = __toESM(require("path"));
79
- var import_child_process2 = require("child_process");
83
+ var import_os2 = __toESM(require("os"));
84
+ var import_path4 = __toESM(require("path"));
85
+ var import_child_process3 = require("child_process");
80
86
  var import_util = require("util");
81
87
  var import_tooling3 = require("@codacy/tooling");
82
88
 
@@ -238,11 +244,13 @@ function createCodacyConfig(params = {}) {
238
244
  }
239
245
 
240
246
  // src/files.ts
241
- var import_child_process = require("child_process");
247
+ var import_child_process2 = require("child_process");
242
248
  var import_fs = __toESM(require("fs"));
243
249
  var import_path2 = __toESM(require("path"));
250
+ var import_minimatch2 = require("minimatch");
251
+
252
+ // src/glob-matcher.ts
244
253
  var import_minimatch = require("minimatch");
245
- var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
246
254
  var PURE_EXTENSION_PATTERN = /^\*\*\/\*(\.[^*/.]+)$/;
247
255
  function pureExtensionOf(pattern) {
248
256
  const m = PURE_EXTENSION_PATTERN.exec(pattern);
@@ -325,69 +333,9 @@ var CompiledMatcher = class {
325
333
  return false;
326
334
  }
327
335
  };
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
- }
336
+
337
+ // src/git-scope.ts
338
+ var import_child_process = require("child_process");
391
339
  function listStagedFiles(repositoryRoot) {
392
340
  try {
393
341
  const output = (0, import_child_process.execSync)("git diff --cached --name-only --diff-filter=d", {
@@ -417,7 +365,7 @@ function listDiffFiles(repositoryRoot, base) {
417
365
  }
418
366
  function listPrFiles(repositoryRoot) {
419
367
  try {
420
- const output = (0, import_child_process.execSync)("gh pr view --json files --jq '.files[].path'", {
368
+ const output = (0, import_child_process.execSync)('gh pr view --json files --jq ".files[].path"', {
421
369
  cwd: repositoryRoot,
422
370
  encoding: "utf-8",
423
371
  maxBuffer: 10 * 1024 * 1024,
@@ -473,11 +421,145 @@ function detectDefaultBranch(repositoryRoot) {
473
421
  "Cannot auto-detect default branch: no origin/HEAD, origin/main, or origin/master found. Specify a base explicitly with --diff <base>."
474
422
  );
475
423
  }
424
+
425
+ // src/files.ts
426
+ var MAX_FILE_SIZE_BYTES = 150 * 1024;
427
+ var DEPENDENCY_MANIFEST_FILES = /* @__PURE__ */ new Set([
428
+ // JavaScript / TypeScript
429
+ "package.json",
430
+ "package-lock.json",
431
+ "npm-shrinkwrap.json",
432
+ "yarn.lock",
433
+ "pnpm-lock.yaml",
434
+ "bun.lock",
435
+ // Python
436
+ "requirements.txt",
437
+ "Pipfile",
438
+ "Pipfile.lock",
439
+ "poetry.lock",
440
+ "uv.lock",
441
+ // PHP
442
+ "composer.json",
443
+ "composer.lock",
444
+ // Ruby
445
+ "Gemfile.lock",
446
+ // Rust
447
+ "Cargo.toml",
448
+ "Cargo.lock",
449
+ // Go
450
+ "go.mod",
451
+ "go.sum",
452
+ // .NET
453
+ "packages.config",
454
+ "packages.lock.json",
455
+ "Directory.Packages.props",
456
+ "Packages.props",
457
+ // Java / Scala
458
+ "pom.xml",
459
+ "gradle.lockfile",
460
+ "build.gradle",
461
+ "build.gradle.kts",
462
+ "build.sbt.lock",
463
+ // Dart / Flutter
464
+ "pubspec.yaml",
465
+ "pubspec.lock",
466
+ // Elixir
467
+ "mix.lock",
468
+ // Swift / CocoaPods
469
+ "Package.resolved",
470
+ "Podfile.lock",
471
+ // C / C++ (Conan)
472
+ "conan.lock",
473
+ // Erlang
474
+ "rebar.lock"
475
+ ]);
476
+ var DEPENDENCY_MANIFEST_SUFFIXES = [".deps.json"];
477
+ function isDependencyManifest(relativePath) {
478
+ const base = relativePath.split("/").pop() ?? "";
479
+ if (DEPENDENCY_MANIFEST_FILES.has(base)) return true;
480
+ return DEPENDENCY_MANIFEST_SUFFIXES.some((suffix) => base.endsWith(suffix));
481
+ }
482
+ function listRepoFiles(repositoryRoot) {
483
+ try {
484
+ const tracked = (0, import_child_process2.execSync)("git ls-files", {
485
+ cwd: repositoryRoot,
486
+ encoding: "utf-8",
487
+ maxBuffer: 50 * 1024 * 1024
488
+ }).trim();
489
+ const untracked = (0, import_child_process2.execSync)("git ls-files --others --exclude-standard", {
490
+ cwd: repositoryRoot,
491
+ encoding: "utf-8",
492
+ maxBuffer: 50 * 1024 * 1024
493
+ }).trim();
494
+ const files = /* @__PURE__ */ new Set();
495
+ for (const f of tracked.split("\n")) {
496
+ if (f) files.add(f);
497
+ }
498
+ for (const f of untracked.split("\n")) {
499
+ if (f) files.add(f);
500
+ }
501
+ return Array.from(files).sort();
502
+ } catch {
503
+ return [];
504
+ }
505
+ }
506
+ function filterOversizedFiles(files, repositoryRoot, logger) {
507
+ const result = [];
508
+ const skipped = [];
509
+ for (const f of files) {
510
+ try {
511
+ const stat = import_fs.default.statSync(import_path2.default.join(repositoryRoot, f));
512
+ if (stat.size > MAX_FILE_SIZE_BYTES) {
513
+ if (isDependencyManifest(f)) {
514
+ logger?.debug("Keeping oversized dependency manifest", {
515
+ file: f,
516
+ sizeKB: Math.round(stat.size / 1024),
517
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
518
+ });
519
+ result.push(f);
520
+ continue;
521
+ }
522
+ skipped.push({ filePath: f, sizeBytes: stat.size });
523
+ logger?.debug("Skipping oversized file", {
524
+ file: f,
525
+ sizeKB: Math.round(stat.size / 1024),
526
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
527
+ });
528
+ continue;
529
+ }
530
+ } catch {
531
+ }
532
+ result.push(f);
533
+ }
534
+ if (skipped.length > 0) {
535
+ logger?.info("Filtered oversized files", {
536
+ skipped: skipped.length,
537
+ remaining: result.length,
538
+ limitKB: MAX_FILE_SIZE_BYTES / 1024
539
+ });
540
+ }
541
+ return { files: result, skipped };
542
+ }
543
+ function applyExclusions(files, globalExclude, toolExclude) {
544
+ if (globalExclude.length === 0 && toolExclude.length === 0) return files;
545
+ const matcher = new CompiledMatcher([...globalExclude, ...toolExclude]);
546
+ if (matcher.isEmpty) return files;
547
+ return files.filter((file) => !matcher.matches(file, import_path2.default.basename(file)));
548
+ }
549
+ function routeFilesToTool(files, adapter, toolConfig) {
550
+ const patterns = [...adapter.filePatterns];
551
+ if (toolConfig?.extraFilePatterns) {
552
+ patterns.push(...toolConfig.extraFilePatterns);
553
+ }
554
+ const matcher = new CompiledMatcher(patterns);
555
+ if (matcher.matchAll) return files;
556
+ return files.filter((file) => matcher.matches(file, import_path2.default.basename(file)));
557
+ }
476
558
  function filterByTargetFiles(allFiles, targetPatterns) {
477
559
  if (targetPatterns.length === 0) return allFiles;
478
560
  const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
479
561
  const exact = new Set(normalized);
480
- const globs = normalized.map((p) => new import_minimatch.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
562
+ const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
481
563
  const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
482
564
  return allFiles.filter(
483
565
  (f) => (
@@ -491,19 +573,62 @@ function filterByTargetFiles(allFiles, targetPatterns) {
491
573
 
492
574
  // src/executor.ts
493
575
  var import_tooling2 = require("@codacy/tooling");
494
- async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
576
+ var HARD_TIMEOUT_GRACE_MS = 2e3;
577
+ function anySignal(a, b) {
578
+ if (typeof AbortSignal.any === "function") return AbortSignal.any([a, b]);
579
+ const composite = new AbortController();
580
+ if (a.aborted || b.aborted) {
581
+ composite.abort();
582
+ } else {
583
+ const onAbort = () => composite.abort();
584
+ a.addEventListener("abort", onAbort, { once: true });
585
+ b.addEventListener("abort", onAbort, { once: true });
586
+ }
587
+ return composite.signal;
588
+ }
589
+ function cancelledResult(adapter) {
590
+ const now = (/* @__PURE__ */ new Date()).toISOString();
591
+ return {
592
+ metadata: { startedAt: now, completedAt: now, durationMs: 0 },
593
+ issues: [],
594
+ errors: [
595
+ {
596
+ toolId: adapter.id,
597
+ phase: "toolInvoke",
598
+ kind: "Cancelled",
599
+ message: `Analysis cancelled by caller; ${adapter.id} was not run`,
600
+ level: "error"
601
+ }
602
+ ],
603
+ summary: {
604
+ toolId: adapter.id,
605
+ status: "skipped",
606
+ issueCount: 0,
607
+ errorCount: 1,
608
+ durationMs: 0,
609
+ filesAnalyzed: 0
610
+ }
611
+ };
612
+ }
613
+ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs, callerSignal) {
495
614
  const startTime = Date.now();
496
615
  ctx.logger.debug("runTool started", {
497
616
  toolId: adapter.id,
498
617
  fileCount: ctx.targetFiles.length,
499
618
  timeoutMs
500
619
  });
620
+ const abortController = new AbortController();
621
+ ctx.signal = callerSignal ? anySignal(callerSignal, abortController.signal) : abortController.signal;
622
+ const abortTimer = setTimeout(() => abortController.abort(), timeoutMs);
623
+ abortTimer.unref?.();
624
+ let hardTimer;
501
625
  try {
502
626
  const timeoutPromise = new Promise((_resolve, reject) => {
503
- setTimeout(
627
+ hardTimer = setTimeout(
504
628
  () => reject(new Error(`TIMEOUT: Tool ${adapter.id} exceeded ${(0, import_tooling2.formatDuration)(timeoutMs)}`)),
505
- timeoutMs
629
+ timeoutMs + HARD_TIMEOUT_GRACE_MS
506
630
  );
631
+ hardTimer.unref?.();
507
632
  });
508
633
  const result = await Promise.race([adapter.analyze(ctx, codacyToolConfig), timeoutPromise]);
509
634
  ctx.logger.debug("runTool completed", {
@@ -514,8 +639,14 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
514
639
  return result;
515
640
  } catch (err) {
516
641
  const durationMs = Date.now() - startTime;
517
- const isTimeout = err instanceof Error && err.message.includes("TIMEOUT");
518
- if (isTimeout) {
642
+ const isCancelled = callerSignal?.aborted === true;
643
+ const isTimeout = !isCancelled && err instanceof Error && err.message.includes("TIMEOUT");
644
+ if (isCancelled) {
645
+ ctx.logger.error("Tool cancelled by caller", {
646
+ toolId: adapter.id,
647
+ elapsedMs: durationMs
648
+ });
649
+ } else if (isTimeout) {
519
650
  ctx.logger.error("Tool timed out", { toolId: adapter.id, timeoutMs, elapsedMs: durationMs });
520
651
  } else {
521
652
  ctx.logger.error("Tool execution failed", {
@@ -525,6 +656,8 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
525
656
  });
526
657
  }
527
658
  const now = /* @__PURE__ */ new Date();
659
+ const kind = isCancelled ? "Cancelled" : isTimeout ? "Timeout" : "InvocationError";
660
+ const message = isCancelled ? `Tool ${adapter.id} cancelled by caller` : isTimeout ? `Tool ${adapter.id} exceeded timeout of ${(0, import_tooling2.formatDuration)(timeoutMs)}` : `Tool ${adapter.id} failed: ${err instanceof Error ? err.message : String(err)}`;
528
661
  return {
529
662
  metadata: {
530
663
  startedAt: new Date(now.getTime() - durationMs).toISOString(),
@@ -536,8 +669,8 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
536
669
  {
537
670
  toolId: adapter.id,
538
671
  phase: "toolInvoke",
539
- kind: isTimeout ? "Timeout" : "InvocationError",
540
- message: isTimeout ? `Tool ${adapter.id} exceeded timeout of ${(0, import_tooling2.formatDuration)(timeoutMs)}` : `Tool ${adapter.id} failed: ${err instanceof Error ? err.message : String(err)}`,
672
+ kind,
673
+ message,
541
674
  level: "error"
542
675
  }
543
676
  ],
@@ -550,17 +683,24 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
550
683
  filesAnalyzed: ctx.targetFiles.length
551
684
  }
552
685
  };
686
+ } finally {
687
+ clearTimeout(abortTimer);
688
+ if (hardTimer) clearTimeout(hardTimer);
553
689
  }
554
690
  }
555
- async function runToolsSequential(tools, timeoutMs, callbacks) {
691
+ async function runToolsSequential(tools, timeoutMs, callbacks, callerSignal) {
556
692
  const results = [];
557
693
  for (const { adapter, ctx, codacyToolConfig } of tools) {
694
+ if (callerSignal?.aborted) {
695
+ results.push(cancelledResult(adapter));
696
+ continue;
697
+ }
558
698
  if (callbacks?.onToolProgress) {
559
699
  const cb = callbacks.onToolProgress;
560
700
  ctx.onProgress = (message) => cb(adapter.id, adapter.displayName, message);
561
701
  }
562
702
  callbacks?.onToolStart?.(adapter.id, adapter.displayName);
563
- const result = await runTool(adapter, ctx, codacyToolConfig, timeoutMs);
703
+ const result = await runTool(adapter, ctx, codacyToolConfig, timeoutMs, callerSignal);
564
704
  callbacks?.onToolComplete?.(
565
705
  adapter.id,
566
706
  adapter.displayName,
@@ -571,19 +711,23 @@ async function runToolsSequential(tools, timeoutMs, callbacks) {
571
711
  }
572
712
  return results;
573
713
  }
574
- async function runToolsParallel(tools, timeoutMs, concurrency, callbacks) {
714
+ async function runToolsParallel(tools, timeoutMs, concurrency, callbacks, callerSignal) {
575
715
  const results = new Array(tools.length);
576
716
  let nextIndex = 0;
577
717
  async function worker() {
578
718
  while (nextIndex < tools.length) {
579
719
  const index = nextIndex++;
580
720
  const { adapter, ctx, codacyToolConfig } = tools[index];
721
+ if (callerSignal?.aborted) {
722
+ results[index] = cancelledResult(adapter);
723
+ continue;
724
+ }
581
725
  if (callbacks?.onToolProgress) {
582
726
  const cb = callbacks.onToolProgress;
583
727
  ctx.onProgress = (message) => cb(adapter.id, adapter.displayName, message);
584
728
  }
585
729
  callbacks?.onToolStart?.(adapter.id, adapter.displayName);
586
- results[index] = await runTool(adapter, ctx, codacyToolConfig, timeoutMs);
730
+ results[index] = await runTool(adapter, ctx, codacyToolConfig, timeoutMs, callerSignal);
587
731
  callbacks?.onToolComplete?.(
588
732
  adapter.id,
589
733
  adapter.displayName,
@@ -597,6 +741,124 @@ async function runToolsParallel(tools, timeoutMs, concurrency, callbacks) {
597
741
  return results;
598
742
  }
599
743
 
744
+ // src/logger.ts
745
+ var import_fs2 = __toESM(require("fs"));
746
+ var import_path3 = __toESM(require("path"));
747
+ var import_os = __toESM(require("os"));
748
+ var LEVEL_ORDER = {
749
+ debug: 0,
750
+ info: 1,
751
+ warning: 2,
752
+ error: 3
753
+ };
754
+ function createNullLogger() {
755
+ return {
756
+ debug: () => {
757
+ },
758
+ info: () => {
759
+ },
760
+ warn: () => {
761
+ },
762
+ error: () => {
763
+ }
764
+ };
765
+ }
766
+ function rotateLogFiles(logsDir, maxFiles) {
767
+ let entries2;
768
+ try {
769
+ entries2 = import_fs2.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
770
+ } catch {
771
+ return;
772
+ }
773
+ if (entries2.length <= maxFiles) return;
774
+ entries2.sort();
775
+ const toRemove = entries2.slice(0, entries2.length - maxFiles);
776
+ for (const f of toRemove) {
777
+ try {
778
+ import_fs2.default.unlinkSync(import_path3.default.join(logsDir, f));
779
+ } catch {
780
+ }
781
+ }
782
+ }
783
+ var fileSeq = 0;
784
+ function fileTimestamp() {
785
+ return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${fileSeq++}`;
786
+ }
787
+ function enrichMessage(message, fields) {
788
+ if (!fields) return message;
789
+ const parts = [];
790
+ if (fields.toolId) parts.push(String(fields.toolId));
791
+ if (fields.version) parts.push(`v${fields.version}`);
792
+ if (fields.installation) parts.push(String(fields.installation));
793
+ if (fields.configSource) parts.push(String(fields.configSource));
794
+ if (typeof fields.fileCount === "number") parts.push(`${fields.fileCount} files`);
795
+ if (typeof fields.issueCount === "number") parts.push(`${fields.issueCount} issues`);
796
+ if (typeof fields.errorCount === "number" && fields.errorCount > 0)
797
+ parts.push(`${fields.errorCount} errors`);
798
+ if (typeof fields.totalIssues === "number") parts.push(`${fields.totalIssues} issues`);
799
+ if (typeof fields.totalErrors === "number" && fields.totalErrors > 0)
800
+ parts.push(`${fields.totalErrors} errors`);
801
+ if (typeof fields.durationMs === "number")
802
+ parts.push(`${(fields.durationMs / 1e3).toFixed(1)}s`);
803
+ if (fields.reason) parts.push(String(fields.reason));
804
+ if (fields.error) parts.push(String(fields.error));
805
+ if (typeof fields.found === "boolean") parts.push(fields.found ? "found" : "not found");
806
+ if (fields.path && !fields.version) parts.push(String(fields.path));
807
+ if (parts.length === 0) return message;
808
+ return `${message} (${parts.join(", ")})`;
809
+ }
810
+ function createLogger(options = {}) {
811
+ const minLevel = LEVEL_ORDER[options.level ?? "info"];
812
+ const fileEnabled = options.fileEnabled ?? true;
813
+ const logsDir = options.logsDir ?? import_path3.default.join(import_os.default.homedir(), ".codacy", "logs");
814
+ const maxFileSize = options.maxFileSize ?? 10 * 1024 * 1024;
815
+ const maxFiles = options.maxFiles ?? 5;
816
+ const onStderrMessage = options.onStderrMessage;
817
+ let logFd = null;
818
+ let bytesWritten = 0;
819
+ if (fileEnabled) {
820
+ import_fs2.default.mkdirSync(logsDir, { recursive: true });
821
+ rotateLogFiles(logsDir, maxFiles);
822
+ const logFilePath = import_path3.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
823
+ logFd = import_fs2.default.openSync(logFilePath, "a");
824
+ }
825
+ function log(level, message, fields) {
826
+ const numLevel = LEVEL_ORDER[level];
827
+ if (numLevel < minLevel) return;
828
+ if (fileEnabled && logFd !== null) {
829
+ const entry = JSON.stringify({
830
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
831
+ level,
832
+ message,
833
+ ...fields
834
+ });
835
+ const buf = Buffer.from(entry + "\n", "utf-8");
836
+ import_fs2.default.writeSync(logFd, buf);
837
+ bytesWritten += buf.length;
838
+ if (bytesWritten >= maxFileSize) {
839
+ import_fs2.default.closeSync(logFd);
840
+ rotateLogFiles(logsDir, maxFiles);
841
+ const newPath = import_path3.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
842
+ logFd = import_fs2.default.openSync(newPath, "a");
843
+ bytesWritten = 0;
844
+ }
845
+ }
846
+ const enriched = enrichMessage(message, fields);
847
+ if (onStderrMessage) {
848
+ onStderrMessage(enriched, level);
849
+ } else {
850
+ process.stderr.write(` ${enriched}
851
+ `);
852
+ }
853
+ }
854
+ return {
855
+ debug: (msg, fields) => log("debug", msg, fields),
856
+ info: (msg, fields) => log("info", msg, fields),
857
+ warn: (msg, fields) => log("warning", msg, fields),
858
+ error: (msg, fields) => log("error", msg, fields)
859
+ };
860
+ }
861
+
600
862
  // src/summary.ts
601
863
  var NOISY_THRESHOLD = 500;
602
864
  var SEVERITY_ORDER = ["Error", "High", "Warning", "Info"];
@@ -708,17 +970,7 @@ function computePatternSummary(issues, lookup) {
708
970
  }
709
971
 
710
972
  // src/analyze.ts
711
- var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
712
- var nullLogger = {
713
- debug: () => {
714
- },
715
- info: () => {
716
- },
717
- warn: () => {
718
- },
719
- error: () => {
720
- }
721
- };
973
+ var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
722
974
  var CHECK_CONCURRENCY = 12;
723
975
  async function mapWithConcurrency(items, concurrency, task) {
724
976
  const results = new Array(items.length);
@@ -736,9 +988,9 @@ async function mapWithConcurrency(items, concurrency, task) {
736
988
  async function analyze(options) {
737
989
  const startedAt = /* @__PURE__ */ new Date();
738
990
  const mode = options.mode || "standard";
739
- const toolTimeout = options.toolTimeout || 6e5;
991
+ const toolTimeout = options.toolTimeout || 9e5;
740
992
  const parallelTools = options.parallelTools || 1;
741
- const logger = options.logger ?? nullLogger;
993
+ const logger = options.logger ?? createNullLogger();
742
994
  const phaseTimings = {
743
995
  availabilityMs: 0,
744
996
  fileDiscoveryMs: 0,
@@ -751,6 +1003,26 @@ async function analyze(options) {
751
1003
  unavailable: [],
752
1004
  checkedAt: (/* @__PURE__ */ new Date()).toISOString()
753
1005
  };
1006
+ if (options.signal?.aborted) {
1007
+ logger.info("Analysis cancelled by caller before it started");
1008
+ return buildResult(
1009
+ startedAt,
1010
+ options.repositoryRoot,
1011
+ mode,
1012
+ emptyCapability,
1013
+ [],
1014
+ [
1015
+ {
1016
+ toolId: "",
1017
+ phase: "toolInvoke",
1018
+ kind: "Cancelled",
1019
+ message: "Analysis cancelled by caller before it started",
1020
+ level: "error"
1021
+ }
1022
+ ],
1023
+ []
1024
+ );
1025
+ }
754
1026
  try {
755
1027
  await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
756
1028
  cwd: options.repositoryRoot
@@ -814,11 +1086,11 @@ async function analyze(options) {
814
1086
  const requestedTools = options.tools ?? codacyConfig.tools.map((t) => t.toolId);
815
1087
  const adapters = await getRegisteredAdapters(requestedTools);
816
1088
  logger.info("Pipeline started", { mode, toolCount: adapters.length, parallelTools, toolTimeout });
817
- const globalDir = import_path3.default.join(import_os.default.homedir(), ".codacy");
1089
+ const globalDir = import_path4.default.join(import_os2.default.homedir(), ".codacy");
818
1090
  const baseCtx = {
819
1091
  repositoryRoot: options.repositoryRoot,
820
1092
  globalDir,
821
- platform: { os: import_os.default.platform(), arch: import_os.default.arch() },
1093
+ platform: { os: import_os2.default.platform(), arch: import_os2.default.arch() },
822
1094
  logger,
823
1095
  runner: "local"
824
1096
  };
@@ -1096,7 +1368,9 @@ async function analyze(options) {
1096
1368
  allFiles = filterByTargetFiles(allFiles, options.files);
1097
1369
  logger.debug("Filtered by target files", { afterFilter: allFiles.length });
1098
1370
  }
1099
- allFiles = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
1371
+ const oversize = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
1372
+ allFiles = oversize.files;
1373
+ const skippedFiles = oversize.skipped;
1100
1374
  phaseTimings.fileDiscoveryMs = Date.now() - fileDiscoveryStartMs;
1101
1375
  const fileRoutingStartMs = Date.now();
1102
1376
  const readyById = new Map(capability.ready.map((r) => [r.toolId, r]));
@@ -1178,9 +1452,15 @@ async function analyze(options) {
1178
1452
  };
1179
1453
  let results;
1180
1454
  if (parallelTools > 1 && toolRuns.length > 1) {
1181
- results = await runToolsParallel(toolRuns, toolTimeout, parallelTools, callbacks);
1455
+ results = await runToolsParallel(
1456
+ toolRuns,
1457
+ toolTimeout,
1458
+ parallelTools,
1459
+ callbacks,
1460
+ options.signal
1461
+ );
1182
1462
  } else {
1183
- results = await runToolsSequential(toolRuns, toolTimeout, callbacks);
1463
+ results = await runToolsSequential(toolRuns, toolTimeout, callbacks, options.signal);
1184
1464
  }
1185
1465
  phaseTimings.toolExecutionMs = Date.now() - toolExecStartMs;
1186
1466
  const allIssues = normalizeIssues(results.flatMap((r) => r.issues));
@@ -1213,15 +1493,16 @@ async function analyze(options) {
1213
1493
  allErrors,
1214
1494
  allSummaries,
1215
1495
  patternSummary,
1216
- phaseTimings
1496
+ phaseTimings,
1497
+ skippedFiles
1217
1498
  );
1218
1499
  }
1219
1500
  function makeToolContext(base, adapter, targetFiles) {
1220
1501
  return {
1221
1502
  ...base,
1222
1503
  targetFiles,
1223
- toolInstallDir: import_path3.default.join(base.globalDir, "tools", adapter.id),
1224
- generatedConfigDir: import_path3.default.join(base.repositoryRoot, ".codacy", "generated", adapter.id)
1504
+ toolInstallDir: import_path4.default.join(base.globalDir, "tools", adapter.id),
1505
+ generatedConfigDir: import_path4.default.join(base.repositoryRoot, ".codacy", "generated", adapter.id)
1225
1506
  };
1226
1507
  }
1227
1508
  function normalizeIssues(issues) {
@@ -1239,7 +1520,7 @@ function resolveGitScope(repositoryRoot, scope) {
1239
1520
  const base = scope.diff === true ? void 0 : scope.diff;
1240
1521
  return listDiffFiles(repositoryRoot, base);
1241
1522
  }
1242
- function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings) {
1523
+ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings, skippedFiles) {
1243
1524
  const completedAt = /* @__PURE__ */ new Date();
1244
1525
  return {
1245
1526
  metadata: {
@@ -1254,15 +1535,17 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
1254
1535
  issues,
1255
1536
  errors,
1256
1537
  toolResults,
1257
- ...patternSummary ? { patternSummary } : {}
1538
+ ...patternSummary ? { patternSummary } : {},
1539
+ ...skippedFiles && skippedFiles.length > 0 ? { skippedFiles } : {}
1258
1540
  };
1259
1541
  }
1260
1542
 
1261
1543
  // src/format.ts
1262
- var import_path4 = __toESM(require("path"));
1544
+ var import_path5 = __toESM(require("path"));
1263
1545
  var import_tooling4 = require("@codacy/tooling");
1264
1546
  var import_ansis = __toESM(require("ansis"));
1265
1547
  var import_cli_table3 = __toESM(require("cli-table3"));
1548
+ var MAX_SKIPPED_FILES_SHOWN = 5;
1266
1549
  function formatOutput(result, format) {
1267
1550
  switch (format) {
1268
1551
  case "json":
@@ -1420,9 +1703,30 @@ function formatText(result) {
1420
1703
  lines.push(...formatErrorSection(result));
1421
1704
  lines.push("");
1422
1705
  }
1706
+ if (result.skippedFiles && result.skippedFiles.length > 0) {
1707
+ lines.push(...formatSkippedFilesSection(result.skippedFiles));
1708
+ lines.push("");
1709
+ }
1423
1710
  lines.push(formatTextSummary(result, isInspect));
1424
1711
  return lines.join("\n");
1425
1712
  }
1713
+ function formatSkippedFilesSection(skipped) {
1714
+ const limitKB = Math.round(MAX_FILE_SIZE_BYTES / 1024);
1715
+ const out = [""];
1716
+ out.push(
1717
+ import_ansis.default.yellow(
1718
+ `\u26A0 ${skipped.length} file${skipped.length !== 1 ? "s" : ""} skipped (exceeding the ${limitKB} KB size limit):`
1719
+ )
1720
+ );
1721
+ for (const f of skipped.slice(0, MAX_SKIPPED_FILES_SHOWN)) {
1722
+ out.push(import_ansis.default.dim(` - ${f.filePath} (${Math.round(f.sizeBytes / 1024)} KB)`));
1723
+ }
1724
+ const remaining = skipped.length - MAX_SKIPPED_FILES_SHOWN;
1725
+ if (remaining > 0) {
1726
+ out.push(import_ansis.default.dim(` \u2026 and ${remaining} more`));
1727
+ }
1728
+ return out;
1729
+ }
1426
1730
  function formatTextSummary(result, isInspect) {
1427
1731
  const lines = [];
1428
1732
  const inspect = isInspect ?? result.metadata.executionMode === "inspect";
@@ -1564,6 +1868,13 @@ function formatTextSummary(result, isInspect) {
1564
1868
  return lines.join("\n");
1565
1869
  }
1566
1870
  var MAX_ERRORS_PER_GROUP = 5;
1871
+ var MAX_ERROR_MESSAGE_LENGTH = 240;
1872
+ function flattenErrorMessage(message) {
1873
+ return message.replace(/\s+/g, " ").trim();
1874
+ }
1875
+ function truncateErrorMessage(message) {
1876
+ return message.length > MAX_ERROR_MESSAGE_LENGTH ? `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}\u2026` : message;
1877
+ }
1567
1878
  function formatErrorSection(result) {
1568
1879
  const lines = [];
1569
1880
  const displayNameMap = /* @__PURE__ */ new Map();
@@ -1585,13 +1896,14 @@ function formatErrorSection(result) {
1585
1896
  const deduped = [];
1586
1897
  const seen = /* @__PURE__ */ new Map();
1587
1898
  for (const err of groupErrors) {
1588
- const dedupKey = `${err.filePath ?? ""}|||${err.message}`;
1899
+ const flat = flattenErrorMessage(err.message);
1900
+ const dedupKey = `${err.filePath ?? ""}|||${flat}`;
1589
1901
  const idx = seen.get(dedupKey);
1590
1902
  if (idx !== void 0) {
1591
1903
  deduped[idx].count++;
1592
1904
  } else {
1593
1905
  seen.set(dedupKey, deduped.length);
1594
- deduped.push({ filePath: err.filePath, message: err.message, count: 1 });
1906
+ deduped.push({ filePath: err.filePath, message: truncateErrorMessage(flat), count: 1 });
1595
1907
  }
1596
1908
  }
1597
1909
  const shown = deduped.slice(0, MAX_ERRORS_PER_GROUP);
@@ -1655,7 +1967,7 @@ function formatFilesCell(filesRouted) {
1655
1967
  }
1656
1968
  function formatConfigCell(tool, repositoryRoot) {
1657
1969
  if (tool.configSource === "local" && tool.configPath) {
1658
- const rawPath = import_path4.default.isAbsolute(tool.configPath) ? import_path4.default.relative(repositoryRoot, tool.configPath) : tool.configPath;
1970
+ const rawPath = import_path5.default.isAbsolute(tool.configPath) ? import_path5.default.relative(repositoryRoot, tool.configPath) : tool.configPath;
1659
1971
  return rawPath.startsWith("./") ? rawPath : `./${rawPath}`;
1660
1972
  }
1661
1973
  if (tool.configSource === "codacy") return "Codacy";
@@ -1917,13 +2229,13 @@ function applyConfigOperation(op, source, dest) {
1917
2229
 
1918
2230
  // src/init.ts
1919
2231
  var import_promises4 = __toESM(require("fs/promises"));
1920
- var import_path7 = __toESM(require("path"));
1921
- var import_child_process3 = require("child_process");
2232
+ var import_path8 = __toESM(require("path"));
2233
+ var import_child_process4 = require("child_process");
1922
2234
  var import_tooling5 = require("@codacy/tooling");
1923
2235
 
1924
2236
  // src/discover.ts
1925
2237
  var import_promises2 = __toESM(require("fs/promises"));
1926
- var import_path5 = __toESM(require("path"));
2238
+ var import_path6 = __toESM(require("path"));
1927
2239
 
1928
2240
  // src/discover-catalog.ts
1929
2241
  var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
@@ -1953,6 +2265,10 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
1953
2265
  ["@hapi/hapi", { displayName: "Hapi", kind: "framework" }],
1954
2266
  ["hono", { displayName: "Hono", kind: "framework" }],
1955
2267
  ["electron", { displayName: "Electron", kind: "framework" }],
2268
+ // AWS CDK — the v2 umbrella package `aws-cdk-lib`, the CLI package `aws-cdk`,
2269
+ // and the v1 `@aws-cdk/*` construct packages (prefix rule below).
2270
+ ["aws-cdk-lib", { displayName: "AWS CDK", kind: "framework" }],
2271
+ ["aws-cdk", { displayName: "AWS CDK", kind: "framework" }],
1956
2272
  // --- JavaScript / TypeScript — Libraries ---
1957
2273
  ["lodash", { displayName: "Lodash", kind: "library" }],
1958
2274
  ["underscore", { displayName: "Underscore.js", kind: "library" }],
@@ -2005,9 +2321,20 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2005
2321
  ["pino", { displayName: "Pino", kind: "library" }],
2006
2322
  ["winston", { displayName: "Winston", kind: "library" }],
2007
2323
  ["i18next", { displayName: "i18next", kind: "library" }],
2008
- ["react-intl", { displayName: "react-intl", kind: "library" }],
2324
+ // react-intl is the React binding of the FormatJS ecosystem; eslint-plugin-formatjs
2325
+ // lints both, so we report the umbrella name "formatjs" (matches the mapping key).
2326
+ ["react-intl", { displayName: "formatjs", kind: "library" }],
2009
2327
  ["vue-i18n", { displayName: "vue-i18n", kind: "library" }],
2010
2328
  ["next-intl", { displayName: "next-intl", kind: "library" }],
2329
+ // Type checker (eslint-plugin-flowtype). Also detected via a `.flowconfig` file.
2330
+ ["flow-bin", { displayName: "Flow", kind: "library" }],
2331
+ // Legacy JS libraries with dedicated ESLint plugins.
2332
+ ["backbone", { displayName: "Backbone.js", kind: "library" }],
2333
+ ["chai", { displayName: "Chai", kind: "library" }],
2334
+ ["codeceptjs", { displayName: "CodeceptJS", kind: "library" }],
2335
+ ["webdriverio", { displayName: "WebdriverIO", kind: "library" }],
2336
+ ["react-relay", { displayName: "Relay", kind: "library" }],
2337
+ ["relay-runtime", { displayName: "Relay", kind: "library" }],
2011
2338
  // --- JavaScript / TypeScript — Tools ---
2012
2339
  ["typescript", { displayName: "TypeScript", kind: "tool" }],
2013
2340
  ["eslint", { displayName: "ESLint", kind: "tool" }],
@@ -2219,7 +2546,9 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2219
2546
  ["cats-core", { displayName: "Cats", kind: "library" }],
2220
2547
  ["circe-core", { displayName: "Circe", kind: "library" }],
2221
2548
  // --- Dart — Frameworks ---
2222
- // `flutter` and `angular` appear as plain keys in pubspec.yaml.
2549
+ // `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
2550
+ // here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
2551
+ // different framework — so package.json overrides this via MANIFEST_SCOPED_CATALOG.
2223
2552
  ["flutter", { displayName: "Flutter", kind: "framework" }],
2224
2553
  ["flutter_test", { displayName: "Flutter", kind: "framework" }],
2225
2554
  ["angular", { displayName: "Angular", kind: "framework" }],
@@ -2256,10 +2585,13 @@ var DEPENDENCY_PREFIX_RULES = [
2256
2585
  classification: { displayName: "Testing Library", kind: "library" }
2257
2586
  },
2258
2587
  { prefix: "@tanstack/", classification: { displayName: "TanStack", kind: "library" } },
2588
+ { prefix: "@wdio/", classification: { displayName: "WebdriverIO", kind: "library" } },
2589
+ // AWS CDK v1 construct libraries are published under the @aws-cdk/ scope.
2590
+ { prefix: "@aws-cdk/", classification: { displayName: "AWS CDK", kind: "framework" } },
2259
2591
  { prefix: "@reduxjs/", classification: { displayName: "Redux", kind: "library" } },
2260
2592
  { prefix: "@prisma/", classification: { displayName: "Prisma", kind: "library" } },
2261
2593
  { prefix: "@apollo/", classification: { displayName: "Apollo", kind: "library" } },
2262
- { prefix: "@formatjs/", classification: { displayName: "react-intl", kind: "library" } },
2594
+ { prefix: "@formatjs/", classification: { displayName: "formatjs", kind: "library" } },
2263
2595
  { prefix: "@lingui/", classification: { displayName: "Lingui", kind: "library" } },
2264
2596
  { prefix: "@emotion/", classification: { displayName: "Emotion", kind: "library" } },
2265
2597
  { prefix: "@mui/", classification: { displayName: "Material UI", kind: "library" } },
@@ -2302,7 +2634,14 @@ var DEPENDENCY_PREFIX_RULES = [
2302
2634
  { prefix: "MSTest.", classification: { displayName: "MSTest", kind: "library" } },
2303
2635
  { prefix: "Serilog", classification: { displayName: "Serilog", kind: "library" } }
2304
2636
  ];
2305
- function classifyDependency(name) {
2637
+ var MANIFEST_SCOPED_CATALOG = /* @__PURE__ */ new Map([
2638
+ ["package.json", /* @__PURE__ */ new Map([["angular", { displayName: "AngularJS", kind: "framework" }]])]
2639
+ ]);
2640
+ function classifyDependency(name, manifestBasename) {
2641
+ if (manifestBasename) {
2642
+ const scoped = MANIFEST_SCOPED_CATALOG.get(manifestBasename)?.get(name);
2643
+ if (scoped) return scoped;
2644
+ }
2306
2645
  const exact = DEPENDENCY_CATALOG.get(name);
2307
2646
  if (exact) return exact;
2308
2647
  for (const rule of DEPENDENCY_PREFIX_RULES) {
@@ -2310,6 +2649,91 @@ function classifyDependency(name) {
2310
2649
  }
2311
2650
  return void 0;
2312
2651
  }
2652
+ function pathBasename(filePath) {
2653
+ return filePath.split("/").pop() ?? filePath;
2654
+ }
2655
+ var FILE_SIGNAL_RULES = [
2656
+ {
2657
+ name: ".flowconfig",
2658
+ matches: (f) => pathBasename(f) === ".flowconfig",
2659
+ classification: { displayName: "Flow", kind: "library" }
2660
+ },
2661
+ {
2662
+ name: "cdk.json",
2663
+ matches: (f) => pathBasename(f) === "cdk.json",
2664
+ classification: { displayName: "AWS CDK", kind: "framework" }
2665
+ },
2666
+ {
2667
+ name: ".meteor/release",
2668
+ matches: (f) => f === ".meteor/release" || f.endsWith("/.meteor/release"),
2669
+ classification: { displayName: "Meteor", kind: "framework" }
2670
+ },
2671
+ {
2672
+ name: "sfdx-project.json",
2673
+ matches: (f) => pathBasename(f) === "sfdx-project.json",
2674
+ classification: { displayName: "Salesforce", kind: "platform" }
2675
+ },
2676
+ {
2677
+ name: "lwc.config.json",
2678
+ matches: (f) => pathBasename(f) === "lwc.config.json",
2679
+ classification: { displayName: "Lightning Web Components", kind: "platform" }
2680
+ },
2681
+ {
2682
+ name: "suitecloud.config.js",
2683
+ matches: (f) => pathBasename(f) === "suitecloud.config.js",
2684
+ classification: { displayName: "SuiteScript", kind: "platform" }
2685
+ }
2686
+ ];
2687
+ function detectFileSignals(files) {
2688
+ const result = [];
2689
+ for (const rule of FILE_SIGNAL_RULES) {
2690
+ const hit = files.find((f) => rule.matches(f));
2691
+ if (!hit) continue;
2692
+ result.push({
2693
+ name: rule.name,
2694
+ displayName: rule.classification.displayName,
2695
+ kind: rule.classification.kind,
2696
+ source: "file",
2697
+ manifestFile: hit
2698
+ });
2699
+ }
2700
+ return result;
2701
+ }
2702
+ var PACKAGE_JSON_SIGNAL_DISPLAY_NAMES = ["ESLint Plugin"];
2703
+ function isEslintPluginName(name) {
2704
+ return /^eslint-plugin-/.test(name) || /^@[^/]+\/eslint-plugin(-.*)?$/.test(name);
2705
+ }
2706
+ function detectPackageJsonSignals(content) {
2707
+ let pkg;
2708
+ try {
2709
+ pkg = JSON.parse(content);
2710
+ } catch {
2711
+ return [];
2712
+ }
2713
+ const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
2714
+ if (name && isEslintPluginName(name)) {
2715
+ return [
2716
+ {
2717
+ name,
2718
+ displayName: "ESLint Plugin",
2719
+ kind: "tool",
2720
+ source: "manifest",
2721
+ manifestFile: "package.json"
2722
+ }
2723
+ ];
2724
+ }
2725
+ return [];
2726
+ }
2727
+ function getKnownDisplayNames() {
2728
+ const names = /* @__PURE__ */ new Set();
2729
+ for (const { displayName } of DEPENDENCY_CATALOG.values()) names.add(displayName);
2730
+ for (const scoped of MANIFEST_SCOPED_CATALOG.values())
2731
+ for (const { displayName } of scoped.values()) names.add(displayName);
2732
+ for (const rule of DEPENDENCY_PREFIX_RULES) names.add(rule.classification.displayName);
2733
+ for (const rule of FILE_SIGNAL_RULES) names.add(rule.classification.displayName);
2734
+ for (const name of PACKAGE_JSON_SIGNAL_DISPLAY_NAMES) names.add(name);
2735
+ return names;
2736
+ }
2313
2737
  var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
2314
2738
  "Dockerfile",
2315
2739
  "docker-compose.yml",
@@ -2679,7 +3103,8 @@ async function discover(repositoryRoot, options) {
2679
3103
  let frameworks = [];
2680
3104
  let libraries = [];
2681
3105
  if (!options?.skipDependencies) {
2682
- const allDeps = await extractDependencies(repositoryRoot, files);
3106
+ const manifestDeps = await extractDependencies(repositoryRoot, files);
3107
+ const allDeps = [...manifestDeps, ...detectFileSignals(files)];
2683
3108
  frameworks = deduplicateDeps(allDeps.filter((d) => d.kind === "framework"));
2684
3109
  libraries = deduplicateDeps(
2685
3110
  allDeps.filter((d) => d.kind === "library" || d.kind === "platform" || d.kind === "tool")
@@ -2692,7 +3117,7 @@ async function discover(repositoryRoot, options) {
2692
3117
  frameworks,
2693
3118
  libraries,
2694
3119
  metadata: {
2695
- repositoryRoot: import_path5.default.resolve(repositoryRoot),
3120
+ repositoryRoot: import_path6.default.resolve(repositoryRoot),
2696
3121
  totalFiles: files.length,
2697
3122
  scannedAt: (/* @__PURE__ */ new Date()).toISOString()
2698
3123
  }
@@ -2701,7 +3126,7 @@ async function discover(repositoryRoot, options) {
2701
3126
  function extractFilePatterns(files) {
2702
3127
  const extensions = /* @__PURE__ */ new Set();
2703
3128
  for (const file of files) {
2704
- const ext = import_path5.default.extname(file).toLowerCase();
3129
+ const ext = import_path6.default.extname(file).toLowerCase();
2705
3130
  if (ext) {
2706
3131
  extensions.add(`*${ext}`);
2707
3132
  }
@@ -2734,13 +3159,14 @@ async function extractDependencies(repositoryRoot, files) {
2734
3159
  if (!parser) continue;
2735
3160
  let content;
2736
3161
  try {
2737
- content = await import_promises2.default.readFile(import_path5.default.join(repositoryRoot, filePath), "utf-8");
3162
+ content = await import_promises2.default.readFile(import_path6.default.join(repositoryRoot, filePath), "utf-8");
2738
3163
  } catch {
2739
3164
  continue;
2740
3165
  }
3166
+ const basename = filePath.split("/").pop() ?? filePath;
2741
3167
  const rawDeps = parser(content);
2742
3168
  for (const raw of rawDeps) {
2743
- const classification = classifyDependency(raw.name);
3169
+ const classification = classifyDependency(raw.name, basename);
2744
3170
  if (classification) {
2745
3171
  allDeps.push({
2746
3172
  name: raw.name,
@@ -2752,6 +3178,11 @@ async function extractDependencies(repositoryRoot, files) {
2752
3178
  });
2753
3179
  }
2754
3180
  }
3181
+ if (basename === "package.json") {
3182
+ for (const signal of detectPackageJsonSignals(content)) {
3183
+ allDeps.push({ ...signal, manifestFile: filePath });
3184
+ }
3185
+ }
2755
3186
  }
2756
3187
  return allDeps;
2757
3188
  }
@@ -2769,14 +3200,14 @@ function deduplicateDeps(deps) {
2769
3200
 
2770
3201
  // src/codacy-yaml.ts
2771
3202
  var import_promises3 = __toESM(require("fs/promises"));
2772
- var import_path6 = __toESM(require("path"));
3203
+ var import_path7 = __toESM(require("path"));
2773
3204
  var import_yaml = require("yaml");
2774
3205
  async function readCodacyYaml(repositoryRoot) {
2775
3206
  const candidates = [".codacy.yaml", ".codacy.yml"];
2776
3207
  let raw = null;
2777
3208
  for (const filename of candidates) {
2778
3209
  try {
2779
- raw = await import_promises3.default.readFile(import_path6.default.join(repositoryRoot, filename), "utf-8");
3210
+ raw = await import_promises3.default.readFile(import_path7.default.join(repositoryRoot, filename), "utf-8");
2780
3211
  break;
2781
3212
  } catch {
2782
3213
  }
@@ -3034,13 +3465,13 @@ var getQueryString = (params) => {
3034
3465
  };
3035
3466
  var getUrl = (config, options) => {
3036
3467
  const encoder = config.ENCODE_PATH || encodeURI;
3037
- const path9 = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => {
3468
+ const path10 = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => {
3038
3469
  if (options.path?.hasOwnProperty(group)) {
3039
3470
  return encoder(String(options.path[group]));
3040
3471
  }
3041
3472
  return substring;
3042
3473
  });
3043
- const url = `${config.BASE}${path9}`;
3474
+ const url = `${config.BASE}${path10}`;
3044
3475
  if (options.query) {
3045
3476
  return `${url}${getQueryString(options.query)}`;
3046
3477
  }
@@ -6422,7 +6853,7 @@ async function detectLanguages(repositoryRoot, precomputedFiles) {
6422
6853
  files = precomputedFiles;
6423
6854
  } else {
6424
6855
  try {
6425
- const output = (0, import_child_process3.execSync)("git ls-files", {
6856
+ const output = (0, import_child_process4.execSync)("git ls-files", {
6426
6857
  cwd: repositoryRoot,
6427
6858
  encoding: "utf-8",
6428
6859
  maxBuffer: 50 * 1024 * 1024
@@ -6434,8 +6865,8 @@ async function detectLanguages(repositoryRoot, precomputedFiles) {
6434
6865
  }
6435
6866
  const detected = /* @__PURE__ */ new Set();
6436
6867
  for (const file of files) {
6437
- const basename = import_path7.default.basename(file);
6438
- const ext = import_path7.default.extname(file).toLowerCase();
6868
+ const basename = import_path8.default.basename(file);
6869
+ const ext = import_path8.default.extname(file).toLowerCase();
6439
6870
  for (const [lang, config] of Object.entries(import_tooling5.LANGUAGE_CONFIG)) {
6440
6871
  if (config.extensions.includes(ext)) {
6441
6872
  detected.add(lang);
@@ -6452,12 +6883,12 @@ async function detectLocalConfigFiles(repositoryRoot, adapters) {
6452
6883
  adapters.map(async (adapter) => {
6453
6884
  const result = await adapter.checkLocalConfigurationFile({
6454
6885
  repositoryRoot,
6455
- generatedConfigDir: import_path7.default.join(repositoryRoot, ".codacy", "generated"),
6456
- globalDir: import_path7.default.join(process.env.HOME ?? "~", ".codacy"),
6457
- toolInstallDir: import_path7.default.join(process.env.HOME ?? "~", ".codacy", "tools", adapter.id),
6886
+ generatedConfigDir: import_path8.default.join(repositoryRoot, ".codacy", "generated"),
6887
+ globalDir: import_path8.default.join(process.env.HOME ?? "~", ".codacy"),
6888
+ toolInstallDir: import_path8.default.join(process.env.HOME ?? "~", ".codacy", "tools", adapter.id),
6458
6889
  targetFiles: [],
6459
6890
  platform: { os: process.platform, arch: process.arch },
6460
- logger: silentLogger,
6891
+ logger: createNullLogger(),
6461
6892
  runner: "local"
6462
6893
  });
6463
6894
  return { toolId: adapter.id, result };
@@ -6470,16 +6901,6 @@ async function detectLocalConfigFiles(repositoryRoot, adapters) {
6470
6901
  }
6471
6902
  return found;
6472
6903
  }
6473
- var silentLogger = {
6474
- debug: () => {
6475
- },
6476
- info: () => {
6477
- },
6478
- warn: () => {
6479
- },
6480
- error: () => {
6481
- }
6482
- };
6483
6904
  async function enrichWithCodacyYaml(config, repositoryRoot, adapters) {
6484
6905
  const yamlConfig = await readCodacyYaml(repositoryRoot);
6485
6906
  if (!yamlConfig) return { config, codacyYamlApplied: false };
@@ -6658,7 +7079,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
6658
7079
  };
6659
7080
  }
6660
7081
  async function initContainerConfig(repositoryRoot, adapters) {
6661
- const codacyrcPath = import_path7.default.join(repositoryRoot, ".codacyrc");
7082
+ const codacyrcPath = import_path8.default.join(repositoryRoot, ".codacyrc");
6662
7083
  let raw;
6663
7084
  try {
6664
7085
  raw = await import_promises4.default.readFile(codacyrcPath, "utf-8");
@@ -6989,110 +7410,68 @@ function parseAutoFilters(raw) {
6989
7410
  return { filters: { severities, categories }, unknown };
6990
7411
  }
6991
7412
 
6992
- // src/logger.ts
6993
- var import_fs2 = __toESM(require("fs"));
6994
- var import_path8 = __toESM(require("path"));
6995
- var import_os2 = __toESM(require("os"));
6996
- var LEVEL_ORDER = {
6997
- debug: 0,
6998
- info: 1,
6999
- warning: 2,
7000
- error: 3
7001
- };
7002
- function rotateLogFiles(logsDir, maxFiles) {
7003
- let entries2;
7413
+ // src/credentials.ts
7414
+ var import_crypto = __toESM(require("crypto"));
7415
+ var import_promises5 = __toESM(require("fs/promises"));
7416
+ var import_os3 = __toESM(require("os"));
7417
+ var import_path9 = __toESM(require("path"));
7418
+ function credentialsPath() {
7419
+ return import_path9.default.join(import_os3.default.homedir(), ".codacy", "credentials");
7420
+ }
7421
+ function deriveKey(salt) {
7422
+ const info = import_os3.default.userInfo();
7423
+ const machineKey = [import_os3.default.hostname(), info.username, import_os3.default.homedir(), import_os3.default.platform()].join("|");
7424
+ return import_crypto.default.pbkdf2Sync(machineKey, salt, 1e5, 32, "sha512");
7425
+ }
7426
+ async function saveCredentials(token) {
7427
+ const credentialsFile = credentialsPath();
7428
+ const salt = import_crypto.default.randomBytes(32);
7429
+ const iv = import_crypto.default.randomBytes(16);
7430
+ const key = deriveKey(salt);
7431
+ const cipher = import_crypto.default.createCipheriv("aes-256-gcm", key, iv);
7432
+ const encrypted = Buffer.concat([cipher.update(token, "utf-8"), cipher.final()]);
7433
+ const authTag = cipher.getAuthTag();
7434
+ const payload = JSON.stringify({
7435
+ salt: salt.toString("hex"),
7436
+ iv: iv.toString("hex"),
7437
+ authTag: authTag.toString("hex"),
7438
+ encrypted: encrypted.toString("hex")
7439
+ });
7440
+ await import_promises5.default.mkdir(import_path9.default.dirname(credentialsFile), { recursive: true });
7441
+ await import_promises5.default.writeFile(credentialsFile, payload, { encoding: "utf-8", mode: 384 });
7442
+ }
7443
+ async function loadCredentials() {
7004
7444
  try {
7005
- entries2 = import_fs2.default.readdirSync(logsDir).filter((f) => f.startsWith("analysis-") && f.endsWith(".log"));
7445
+ const raw = await import_promises5.default.readFile(credentialsPath(), "utf-8");
7446
+ const data = JSON.parse(raw);
7447
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
7448
+ const salt = Buffer.from(data.salt, "hex");
7449
+ const iv = Buffer.from(data.iv, "hex");
7450
+ const authTag = Buffer.from(data.authTag, "hex");
7451
+ const encrypted = Buffer.from(data.encrypted, "hex");
7452
+ const key = deriveKey(salt);
7453
+ const decipher = import_crypto.default.createDecipheriv("aes-256-gcm", key, iv);
7454
+ decipher.setAuthTag(authTag);
7455
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
7456
+ return decrypted.toString("utf-8");
7006
7457
  } catch {
7007
- return;
7008
- }
7009
- if (entries2.length <= maxFiles) return;
7010
- entries2.sort();
7011
- const toRemove = entries2.slice(0, entries2.length - maxFiles);
7012
- for (const f of toRemove) {
7013
- try {
7014
- import_fs2.default.unlinkSync(import_path8.default.join(logsDir, f));
7015
- } catch {
7016
- }
7458
+ return null;
7017
7459
  }
7018
7460
  }
7019
- var fileSeq = 0;
7020
- function fileTimestamp() {
7021
- return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${fileSeq++}`;
7022
- }
7023
- function enrichMessage(message, fields) {
7024
- if (!fields) return message;
7025
- const parts = [];
7026
- if (fields.toolId) parts.push(String(fields.toolId));
7027
- if (fields.version) parts.push(`v${fields.version}`);
7028
- if (fields.installation) parts.push(String(fields.installation));
7029
- if (fields.configSource) parts.push(String(fields.configSource));
7030
- if (typeof fields.fileCount === "number") parts.push(`${fields.fileCount} files`);
7031
- if (typeof fields.issueCount === "number") parts.push(`${fields.issueCount} issues`);
7032
- if (typeof fields.errorCount === "number" && fields.errorCount > 0)
7033
- parts.push(`${fields.errorCount} errors`);
7034
- if (typeof fields.totalIssues === "number") parts.push(`${fields.totalIssues} issues`);
7035
- if (typeof fields.totalErrors === "number" && fields.totalErrors > 0)
7036
- parts.push(`${fields.totalErrors} errors`);
7037
- if (typeof fields.durationMs === "number")
7038
- parts.push(`${(fields.durationMs / 1e3).toFixed(1)}s`);
7039
- if (fields.reason) parts.push(String(fields.reason));
7040
- if (fields.error) parts.push(String(fields.error));
7041
- if (typeof fields.found === "boolean") parts.push(fields.found ? "found" : "not found");
7042
- if (fields.path && !fields.version) parts.push(String(fields.path));
7043
- if (parts.length === 0) return message;
7044
- return `${message} (${parts.join(", ")})`;
7045
- }
7046
- function createLogger(options = {}) {
7047
- const minLevel = LEVEL_ORDER[options.level ?? "info"];
7048
- const fileEnabled = options.fileEnabled ?? true;
7049
- const logsDir = options.logsDir ?? import_path8.default.join(import_os2.default.homedir(), ".codacy", "logs");
7050
- const maxFileSize = options.maxFileSize ?? 10 * 1024 * 1024;
7051
- const maxFiles = options.maxFiles ?? 5;
7052
- const onStderrMessage = options.onStderrMessage;
7053
- let logFd = null;
7054
- let bytesWritten = 0;
7055
- if (fileEnabled) {
7056
- import_fs2.default.mkdirSync(logsDir, { recursive: true });
7057
- rotateLogFiles(logsDir, maxFiles);
7058
- const logFilePath = import_path8.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
7059
- logFd = import_fs2.default.openSync(logFilePath, "a");
7060
- }
7061
- function log(level, message, fields) {
7062
- const numLevel = LEVEL_ORDER[level];
7063
- if (numLevel < minLevel) return;
7064
- if (fileEnabled && logFd !== null) {
7065
- const entry = JSON.stringify({
7066
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7067
- level,
7068
- message,
7069
- ...fields
7070
- });
7071
- const buf = Buffer.from(entry + "\n", "utf-8");
7072
- import_fs2.default.writeSync(logFd, buf);
7073
- bytesWritten += buf.length;
7074
- if (bytesWritten >= maxFileSize) {
7075
- import_fs2.default.closeSync(logFd);
7076
- rotateLogFiles(logsDir, maxFiles);
7077
- const newPath = import_path8.default.join(logsDir, `analysis-${fileTimestamp()}.log`);
7078
- logFd = import_fs2.default.openSync(newPath, "a");
7079
- bytesWritten = 0;
7080
- }
7081
- }
7082
- const enriched = enrichMessage(message, fields);
7083
- if (onStderrMessage) {
7084
- onStderrMessage(enriched, level);
7085
- } else {
7086
- process.stderr.write(` ${enriched}
7087
- `);
7088
- }
7461
+ async function deleteCredentials() {
7462
+ try {
7463
+ await import_promises5.default.unlink(credentialsPath());
7464
+ return true;
7465
+ } catch {
7466
+ return false;
7089
7467
  }
7090
- return {
7091
- debug: (msg, fields) => log("debug", msg, fields),
7092
- info: (msg, fields) => log("info", msg, fields),
7093
- warn: (msg, fields) => log("warning", msg, fields),
7094
- error: (msg, fields) => log("error", msg, fields)
7095
- };
7468
+ }
7469
+ async function resolveApiToken() {
7470
+ const envToken = process.env.CODACY_API_TOKEN;
7471
+ if (envToken) return envToken;
7472
+ const stored = await loadCredentials();
7473
+ if (stored) return stored;
7474
+ throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
7096
7475
  }
7097
7476
  // Annotate the CommonJS export names for ESM import in node:
7098
7477
  0 && (module.exports = {
@@ -7105,12 +7484,15 @@ function createLogger(options = {}) {
7105
7484
  configureApiToken,
7106
7485
  createCodacyConfig,
7107
7486
  createLogger,
7487
+ createNullLogger,
7488
+ deleteCredentials,
7108
7489
  detectLanguages,
7109
7490
  diffConfigs,
7110
7491
  discover,
7111
7492
  formatOutput,
7112
7493
  formatTextSummary,
7113
7494
  getAdapter,
7495
+ getKnownDisplayNames,
7114
7496
  getRegisteredAdapters,
7115
7497
  getRegisteredDescriptors,
7116
7498
  initAutoConfig,
@@ -7123,6 +7505,7 @@ function createLogger(options = {}) {
7123
7505
  listIgnoredFiles,
7124
7506
  listPrFiles,
7125
7507
  listStagedFiles,
7508
+ loadCredentials,
7126
7509
  mergeConfigs,
7127
7510
  parseAutoFilters,
7128
7511
  readCodacyConfig,
@@ -7130,9 +7513,11 @@ function createLogger(options = {}) {
7130
7513
  registerAdapter,
7131
7514
  registerLazyAdapter,
7132
7515
  registerUnsupportedTool,
7516
+ resolveApiToken,
7133
7517
  resolveConfigPath,
7134
7518
  resolveEngineToAdapter,
7135
7519
  rotateLogFiles,
7520
+ saveCredentials,
7136
7521
  validateApiToken,
7137
7522
  writeCodacyConfig,
7138
7523
  writeCodacyConfigFile