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