@codacy/analysis-runner 0.15.0 → 0.17.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 +177 -2
- package/dist/index.js +333 -19
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -586,7 +586,12 @@ interface InitAutoStats {
|
|
|
586
586
|
*
|
|
587
587
|
* Severity and category filters are ANDed across dimensions, ORed within.
|
|
588
588
|
*
|
|
589
|
-
*
|
|
589
|
+
* Tool-level overlap (two *different* tools that lint the same stack, e.g.
|
|
590
|
+
* ESLint and Biome) is resolved by `deduplicateToolFamilies` via each
|
|
591
|
+
* descriptor's `alternativeGroup` — only one member of a group is selected.
|
|
592
|
+
*
|
|
593
|
+
* **Known limitation (v1):** *Pattern*-level overlap within a single tool is
|
|
594
|
+
* not yet resolved — e.g. when both TypeScript and JavaScript are detected,
|
|
590
595
|
* ESLint core rules and their `@typescript-eslint` equivalents may both be
|
|
591
596
|
* enabled, potentially causing duplicate findings. This will be addressed in v2
|
|
592
597
|
* via conflict-resolution metadata in the mapping files.
|
|
@@ -853,4 +858,174 @@ declare function discover(repositoryRoot: string, options?: DiscoverOptions): Pr
|
|
|
853
858
|
*/
|
|
854
859
|
declare function getKnownDisplayNames(): Set<string>;
|
|
855
860
|
|
|
856
|
-
|
|
861
|
+
/**
|
|
862
|
+
* Git repository introspection helpers used by commands that need to know
|
|
863
|
+
* *where* they are (repository root) and *what* they are looking at (the current
|
|
864
|
+
* commit SHA). Kept separate from `git-scope.ts` (which resolves file *sets* for
|
|
865
|
+
* `--staged`/`--diff`/`--pr`); this module answers point questions about the repo.
|
|
866
|
+
*
|
|
867
|
+
* Both helpers shell out with `execFileSync` (never a shell string) so paths and
|
|
868
|
+
* refs can't be interpreted by the shell.
|
|
869
|
+
*/
|
|
870
|
+
/**
|
|
871
|
+
* Resolves the absolute path to the enclosing git repository's root.
|
|
872
|
+
*
|
|
873
|
+
* Wraps `git rev-parse --show-toplevel`. Throws a clear error when `cwd` is not
|
|
874
|
+
* inside a git working tree, so callers can surface an actionable message rather
|
|
875
|
+
* than a raw git failure.
|
|
876
|
+
*/
|
|
877
|
+
declare function resolveRepositoryRoot(cwd?: string): string;
|
|
878
|
+
/**
|
|
879
|
+
* Resolves the SHA of the current `HEAD` commit.
|
|
880
|
+
*
|
|
881
|
+
* Wraps `git rev-parse HEAD`. Used by `upload` to auto-detect the commit when the
|
|
882
|
+
* caller omits `--commit`. Throws when there is no commit yet (empty repo) or the
|
|
883
|
+
* directory is not a git repository, so the command can tell the user to pass
|
|
884
|
+
* `--commit` explicitly.
|
|
885
|
+
*/
|
|
886
|
+
declare function resolveHeadCommit(repositoryRoot: string): string;
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* `upload` — sends an analysis report produced by this CLI to Codacy, attaching
|
|
890
|
+
* its issues to a commit so they surface on Codacy Cloud.
|
|
891
|
+
*
|
|
892
|
+
* ## What it uploads
|
|
893
|
+
*
|
|
894
|
+
* Only reports this CLI produced, in either output format:
|
|
895
|
+
* - `sarif` — our SARIF v2.1.0 document, where `run.tool.driver.name` is the tool
|
|
896
|
+
* id and each `result.ruleId` is already a Codacy pattern id.
|
|
897
|
+
* - `json` — the full `AnalysisResult`, whose `issues[]` carry `toolId`, `patternId`,
|
|
898
|
+
* `severity`, etc.
|
|
899
|
+
*
|
|
900
|
+
* Because our reports already carry Codacy-native pattern ids, no online pattern
|
|
901
|
+
* lookup is needed (unlike the legacy Go CLI, which had to map raw tool rule ids to
|
|
902
|
+
* Codacy patterns). We normalize either format into a flat issue list and build the
|
|
903
|
+
* payload directly.
|
|
904
|
+
*
|
|
905
|
+
* ## Where it uploads
|
|
906
|
+
*
|
|
907
|
+
* The **legacy v2 API** (`https://api.codacy.com/2.0`), matching the Go CLI: one
|
|
908
|
+
* `issuesRemoteResults` POST per tool, then a single `resultsFinal` POST to close the
|
|
909
|
+
* commit. The backend stores the `issues` payload as an opaque blob and a separate
|
|
910
|
+
* downstream engine parses it, so the payload shape must match the Go CLI's exactly.
|
|
911
|
+
*
|
|
912
|
+
* ## Auth
|
|
913
|
+
*
|
|
914
|
+
* Two mutually exclusive modes (see {@link UploadAuth}): a repository token
|
|
915
|
+
* (`project-token` header, commit-scoped URL) or an account token (`api-token`
|
|
916
|
+
* header, provider/owner/repo-scoped URL).
|
|
917
|
+
*/
|
|
918
|
+
/**
|
|
919
|
+
* An issue flattened from either report format into the minimal fields the v2
|
|
920
|
+
* payload needs. `filePath` is relative to the repository root (both our SARIF and
|
|
921
|
+
* JSON emit repo-relative paths, so no recomputation is required).
|
|
922
|
+
*/
|
|
923
|
+
interface NormalizedIssue {
|
|
924
|
+
/** Tool id (our adapter id, e.g. "ESLint9"); becomes the payload's tool grouping. */
|
|
925
|
+
toolId: string;
|
|
926
|
+
/** Codacy pattern id (e.g. "ESLint9_accessor-pairs"). */
|
|
927
|
+
patternId: string;
|
|
928
|
+
/** Repository-relative file path. */
|
|
929
|
+
filePath: string;
|
|
930
|
+
/** 1-based line number. */
|
|
931
|
+
line: number;
|
|
932
|
+
/** Issue message text. */
|
|
933
|
+
message: string;
|
|
934
|
+
/** Codacy level string (Info/Warning/High/Error for json; Info/Warning/Error for sarif). */
|
|
935
|
+
level: string;
|
|
936
|
+
}
|
|
937
|
+
/** Authentication + routing for the upload. */
|
|
938
|
+
type UploadAuth = {
|
|
939
|
+
kind: "repository-token";
|
|
940
|
+
/** Repository (a.k.a. project) token; sent as the `project-token` header. */
|
|
941
|
+
token: string;
|
|
942
|
+
} | {
|
|
943
|
+
kind: "account-token";
|
|
944
|
+
/** Account/user token; sent as the `api-token` header. */
|
|
945
|
+
token: string;
|
|
946
|
+
provider: string;
|
|
947
|
+
owner: string;
|
|
948
|
+
repository: string;
|
|
949
|
+
};
|
|
950
|
+
/** Options for {@link uploadReport}. */
|
|
951
|
+
interface UploadOptions {
|
|
952
|
+
/** Absolute or CWD-relative path to the report file (sarif or json). */
|
|
953
|
+
reportPath: string;
|
|
954
|
+
/** Commit SHA/UUID the results attach to. */
|
|
955
|
+
commitUuid: string;
|
|
956
|
+
auth: UploadAuth;
|
|
957
|
+
/** Progress callback for human-facing messages (stderr in the CLI). */
|
|
958
|
+
onProgress?: (message: string) => void;
|
|
959
|
+
}
|
|
960
|
+
/** Outcome of an upload. */
|
|
961
|
+
interface UploadResult {
|
|
962
|
+
/** Tool short-names that had at least one issue uploaded. */
|
|
963
|
+
toolsUploaded: string[];
|
|
964
|
+
/** Total issues uploaded across all tools. */
|
|
965
|
+
issueCount: number;
|
|
966
|
+
/** Whether the final `resultsFinal` call succeeded. */
|
|
967
|
+
finalized: boolean;
|
|
968
|
+
}
|
|
969
|
+
/** The per-tool payload object (wrapped in a one-element array per request). */
|
|
970
|
+
interface ToolPayload {
|
|
971
|
+
tool: string;
|
|
972
|
+
issues: {
|
|
973
|
+
Success: {
|
|
974
|
+
results: FileResults[];
|
|
975
|
+
};
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
interface FileResults {
|
|
979
|
+
filename: string;
|
|
980
|
+
results: Array<{
|
|
981
|
+
Issue: PayloadIssue;
|
|
982
|
+
}>;
|
|
983
|
+
}
|
|
984
|
+
interface PayloadIssue {
|
|
985
|
+
patternId: {
|
|
986
|
+
value: string;
|
|
987
|
+
};
|
|
988
|
+
filename: string;
|
|
989
|
+
message: {
|
|
990
|
+
text: string;
|
|
991
|
+
};
|
|
992
|
+
level: string;
|
|
993
|
+
location: {
|
|
994
|
+
LineLocation: {
|
|
995
|
+
line: number;
|
|
996
|
+
};
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Resolves the base URL for the legacy v2 results API. Defaults to Codacy SaaS;
|
|
1001
|
+
* override with `CODACY_RESULTS_API_BASE_URL` for self-hosted deployments. Note this
|
|
1002
|
+
* is a different host from `CODACY_API_BASE_URL` (the v3 app API).
|
|
1003
|
+
*/
|
|
1004
|
+
declare function resolveResultsApiBaseUrl(): string;
|
|
1005
|
+
/**
|
|
1006
|
+
* Detects the report format and flattens it into {@link NormalizedIssue}s.
|
|
1007
|
+
* Accepts only reports produced by this CLI (our SARIF or our JSON `AnalysisResult`);
|
|
1008
|
+
* throws a clear error otherwise.
|
|
1009
|
+
*/
|
|
1010
|
+
declare function parseReport(fileContents: string): NormalizedIssue[];
|
|
1011
|
+
/**
|
|
1012
|
+
* Builds one v2 payload per tool, grouping issues by file. `resolveShortName` maps
|
|
1013
|
+
* our internal tool id to the Codacy tool short-name expected in the `tool` field
|
|
1014
|
+
* (which becomes an S3 key segment consumed downstream).
|
|
1015
|
+
*/
|
|
1016
|
+
declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string): ToolPayload[];
|
|
1017
|
+
/**
|
|
1018
|
+
* Uploads an analysis report to Codacy. Reads and parses the report, resolves tool
|
|
1019
|
+
* short-names, POSTs one `issuesRemoteResults` per tool, then a single `resultsFinal`.
|
|
1020
|
+
*
|
|
1021
|
+
* Throws on a failed `issuesRemoteResults` POST (partial uploads shouldn't be
|
|
1022
|
+
* silently finalized). A failed `resultsFinal` is surfaced via `onProgress` and
|
|
1023
|
+
* reflected in the returned {@link UploadResult}, matching the Go CLI's non-fatal
|
|
1024
|
+
* treatment of finalization.
|
|
1025
|
+
*
|
|
1026
|
+
* Proxy/TLS is honored automatically — all `fetch` traffic goes through the global
|
|
1027
|
+
* dispatcher installed by `configureProxy()`.
|
|
1028
|
+
*/
|
|
1029
|
+
declare function uploadReport(options: UploadOptions): Promise<UploadResult>;
|
|
1030
|
+
|
|
1031
|
+
export { type AdapterFactory, type AnalyzeOptions, type AutoFilters, type CategoryFilter, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ConfigOperation, type DiscoverOptions, type ExecutionMode, type IgnoredFilesResult, type InitAutoStats, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, type MergeOptions, type NormalizedIssue, type PatternMetadataLookup, type SeverityFilter, type UploadAuth, type UploadOptions, type UploadResult, analyze, applyCodacyYamlExcludes, applyConfigOperation, buildPayloads, buildToolConfig, clearAdapters, computePatternSummary, configureApiToken, createCodacyConfig, createLogger, createNullLogger, deleteCredentials, detectLanguages, diffConfigs, discover, formatOutput, formatTextSummary, getAdapter, getKnownDisplayNames, getRegisteredAdapters, getRegisteredDescriptors, initAutoConfig, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, intersectConfigs, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, loadCredentials, mergeConfigs, parseAutoFilters, parseReport, pruneEmptyTools, readBaselineConfig, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveBaselinePath, resolveConfigPath, resolveEngineToAdapter, resolveHeadCommit, resolveRepositoryRoot, resolveResultsApiBaseUrl, rotateLogFiles, saveCredentials, updateConfigIncremental, uploadReport, validateApiToken, writeBaselineConfig, writeCodacyConfig, writeCodacyConfigFile };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
analyze: () => analyze,
|
|
34
34
|
applyCodacyYamlExcludes: () => applyCodacyYamlExcludes,
|
|
35
35
|
applyConfigOperation: () => applyConfigOperation,
|
|
36
|
+
buildPayloads: () => buildPayloads,
|
|
36
37
|
buildToolConfig: () => buildToolConfig,
|
|
37
38
|
clearAdapters: () => clearAdapters,
|
|
38
39
|
computePatternSummary: () => computePatternSummary,
|
|
@@ -64,6 +65,7 @@ __export(index_exports, {
|
|
|
64
65
|
loadCredentials: () => loadCredentials,
|
|
65
66
|
mergeConfigs: () => mergeConfigs,
|
|
66
67
|
parseAutoFilters: () => parseAutoFilters,
|
|
68
|
+
parseReport: () => parseReport,
|
|
67
69
|
pruneEmptyTools: () => pruneEmptyTools,
|
|
68
70
|
readBaselineConfig: () => readBaselineConfig,
|
|
69
71
|
readCodacyConfig: () => readCodacyConfig,
|
|
@@ -75,10 +77,14 @@ __export(index_exports, {
|
|
|
75
77
|
resolveBaselinePath: () => resolveBaselinePath,
|
|
76
78
|
resolveConfigPath: () => resolveConfigPath,
|
|
77
79
|
resolveEngineToAdapter: () => resolveEngineToAdapter,
|
|
80
|
+
resolveHeadCommit: () => resolveHeadCommit,
|
|
78
81
|
resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
|
|
82
|
+
resolveRepositoryRoot: () => resolveRepositoryRoot,
|
|
83
|
+
resolveResultsApiBaseUrl: () => resolveResultsApiBaseUrl,
|
|
79
84
|
rotateLogFiles: () => rotateLogFiles,
|
|
80
85
|
saveCredentials: () => saveCredentials,
|
|
81
86
|
updateConfigIncremental: () => updateConfigIncremental,
|
|
87
|
+
uploadReport: () => uploadReport,
|
|
82
88
|
validateApiToken: () => validateApiToken,
|
|
83
89
|
writeBaselineConfig: () => writeBaselineConfig,
|
|
84
90
|
writeCodacyConfig: () => writeCodacyConfig,
|
|
@@ -2314,6 +2320,7 @@ function updateConfigIncremental(base, current, next) {
|
|
|
2314
2320
|
|
|
2315
2321
|
// src/init.ts
|
|
2316
2322
|
var import_promises4 = __toESM(require("fs/promises"));
|
|
2323
|
+
var import_os3 = __toESM(require("os"));
|
|
2317
2324
|
var import_path8 = __toESM(require("path"));
|
|
2318
2325
|
var import_child_process4 = require("child_process");
|
|
2319
2326
|
var import_tooling5 = require("@codacy/tooling");
|
|
@@ -7362,21 +7369,25 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7362
7369
|
filters
|
|
7363
7370
|
);
|
|
7364
7371
|
if (selectedPatterns.length > 0) {
|
|
7365
|
-
|
|
7372
|
+
const toolEntry = {
|
|
7366
7373
|
toolId: adapter.id,
|
|
7367
7374
|
patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
|
|
7368
|
-
}
|
|
7375
|
+
};
|
|
7376
|
+
tools.push(toolEntry);
|
|
7377
|
+
await applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles);
|
|
7369
7378
|
}
|
|
7370
7379
|
}
|
|
7371
7380
|
let selectedUnsupported = 0;
|
|
7372
|
-
const selectedFamilies = new Set(
|
|
7381
|
+
const selectedFamilies = new Set(
|
|
7382
|
+
tools.map((t) => toolGroupKey(t.toolId, descriptorMap.get(t.toolId)?.alternativeGroup))
|
|
7383
|
+
);
|
|
7373
7384
|
for (const desc of descriptors) {
|
|
7374
7385
|
if (!desc.unsupported) continue;
|
|
7375
7386
|
if (!loadUnsupportedPatterns) break;
|
|
7376
7387
|
if (!desc.languages.includes("*") && desc.languages.length > 0 && repoLanguages.size > 0 && !desc.languages.some((lang) => repoLanguages.has(lang))) {
|
|
7377
7388
|
continue;
|
|
7378
7389
|
}
|
|
7379
|
-
const family =
|
|
7390
|
+
const family = toolGroupKey(desc.id, desc.alternativeGroup);
|
|
7380
7391
|
if (selectedFamilies.has(family)) continue;
|
|
7381
7392
|
if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
|
|
7382
7393
|
const patternsMetadata = loadUnsupportedPatterns(desc.id);
|
|
@@ -7420,16 +7431,58 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7420
7431
|
}
|
|
7421
7432
|
};
|
|
7422
7433
|
}
|
|
7434
|
+
async function applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles) {
|
|
7435
|
+
if (!adapter.suggestParameters) return;
|
|
7436
|
+
const logger = {
|
|
7437
|
+
debug() {
|
|
7438
|
+
},
|
|
7439
|
+
info() {
|
|
7440
|
+
},
|
|
7441
|
+
warn(message, fields) {
|
|
7442
|
+
console.error(`[init --auto] ${adapter.id}: ${message}`, fields ?? "");
|
|
7443
|
+
},
|
|
7444
|
+
error(message, fields) {
|
|
7445
|
+
console.error(`[init --auto] ${adapter.id}: ${message}`, fields ?? "");
|
|
7446
|
+
}
|
|
7447
|
+
};
|
|
7448
|
+
try {
|
|
7449
|
+
const globalDir = import_path8.default.join(import_os3.default.homedir(), ".codacy");
|
|
7450
|
+
const ctx = {
|
|
7451
|
+
repositoryRoot,
|
|
7452
|
+
generatedConfigDir: import_path8.default.join(repositoryRoot, ".codacy", "generated", adapter.id),
|
|
7453
|
+
globalDir,
|
|
7454
|
+
toolInstallDir: import_path8.default.join(globalDir, "tools", adapter.id),
|
|
7455
|
+
targetFiles: routeFilesToTool(repoFiles, adapter, toolEntry),
|
|
7456
|
+
platform: { os: process.platform, arch: process.arch },
|
|
7457
|
+
logger,
|
|
7458
|
+
runner: "local"
|
|
7459
|
+
};
|
|
7460
|
+
const suggestions = await adapter.suggestParameters(ctx);
|
|
7461
|
+
if (!suggestions) return;
|
|
7462
|
+
for (const pattern of toolEntry.patterns) {
|
|
7463
|
+
const params = suggestions[pattern.patternId];
|
|
7464
|
+
if (!params) continue;
|
|
7465
|
+
pattern.parameters = { ...pattern.parameters, ...params };
|
|
7466
|
+
}
|
|
7467
|
+
} catch (err) {
|
|
7468
|
+
logger.error("suggestParameters failed", {
|
|
7469
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7470
|
+
});
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7423
7473
|
function toolFamily(id) {
|
|
7424
7474
|
return id.replace(/\d+$/, "");
|
|
7425
7475
|
}
|
|
7476
|
+
function toolGroupKey(id, alternativeGroup) {
|
|
7477
|
+
return alternativeGroup ?? toolFamily(id);
|
|
7478
|
+
}
|
|
7426
7479
|
function deduplicateToolFamilies(candidates, localConfigs) {
|
|
7427
7480
|
const families = /* @__PURE__ */ new Map();
|
|
7428
7481
|
for (const adapter of candidates) {
|
|
7429
|
-
const
|
|
7430
|
-
const group = families.get(
|
|
7482
|
+
const key = toolGroupKey(adapter.id, adapter.alternativeGroup);
|
|
7483
|
+
const group = families.get(key) ?? [];
|
|
7431
7484
|
group.push(adapter);
|
|
7432
|
-
families.set(
|
|
7485
|
+
families.set(key, group);
|
|
7433
7486
|
}
|
|
7434
7487
|
const result = [];
|
|
7435
7488
|
for (const group of families.values()) {
|
|
@@ -7438,16 +7491,28 @@ function deduplicateToolFamilies(candidates, localConfigs) {
|
|
|
7438
7491
|
continue;
|
|
7439
7492
|
}
|
|
7440
7493
|
const withConfig = group.filter((a) => localConfigs.has(a.id));
|
|
7441
|
-
|
|
7442
|
-
|
|
7494
|
+
const pool = withConfig.length > 0 ? withConfig : group;
|
|
7495
|
+
if (pool.length === 1) {
|
|
7496
|
+
result.push(pool[0]);
|
|
7443
7497
|
continue;
|
|
7444
7498
|
}
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
}
|
|
7450
|
-
|
|
7499
|
+
const preferred = pool.filter((a) => a.alternativeGroupDefault);
|
|
7500
|
+
if (preferred.length === 1) {
|
|
7501
|
+
result.push(preferred[0]);
|
|
7502
|
+
continue;
|
|
7503
|
+
}
|
|
7504
|
+
const defaultFamilies = new Set(
|
|
7505
|
+
group.filter((a) => a.alternativeGroupDefault).map((a) => toolFamily(a.id))
|
|
7506
|
+
);
|
|
7507
|
+
const defaultFamilyMembers = defaultFamilies.size === 1 ? pool.filter((a) => defaultFamilies.has(toolFamily(a.id))) : [];
|
|
7508
|
+
const ranked = [...defaultFamilyMembers.length > 0 ? defaultFamilyMembers : pool].sort(
|
|
7509
|
+
(a, b) => {
|
|
7510
|
+
const majorA = parseInt(a.preferredVersion.split(".")[0], 10) || 0;
|
|
7511
|
+
const majorB = parseInt(b.preferredVersion.split(".")[0], 10) || 0;
|
|
7512
|
+
return majorB - majorA;
|
|
7513
|
+
}
|
|
7514
|
+
);
|
|
7515
|
+
result.push(ranked[0]);
|
|
7451
7516
|
}
|
|
7452
7517
|
return result;
|
|
7453
7518
|
}
|
|
@@ -7603,14 +7668,14 @@ var import_tooling6 = require("@codacy/tooling");
|
|
|
7603
7668
|
// src/credentials.ts
|
|
7604
7669
|
var import_crypto = __toESM(require("crypto"));
|
|
7605
7670
|
var import_promises5 = __toESM(require("fs/promises"));
|
|
7606
|
-
var
|
|
7671
|
+
var import_os4 = __toESM(require("os"));
|
|
7607
7672
|
var import_path9 = __toESM(require("path"));
|
|
7608
7673
|
function credentialsPath() {
|
|
7609
|
-
return import_path9.default.join(
|
|
7674
|
+
return import_path9.default.join(import_os4.default.homedir(), ".codacy", "credentials");
|
|
7610
7675
|
}
|
|
7611
7676
|
function deriveKey(salt) {
|
|
7612
|
-
const info =
|
|
7613
|
-
const machineKey = [
|
|
7677
|
+
const info = import_os4.default.userInfo();
|
|
7678
|
+
const machineKey = [import_os4.default.hostname(), info.username, import_os4.default.homedir(), import_os4.default.platform()].join("|");
|
|
7614
7679
|
return import_crypto.default.pbkdf2Sync(machineKey, salt, 1e5, 32, "sha512");
|
|
7615
7680
|
}
|
|
7616
7681
|
async function saveCredentials(token) {
|
|
@@ -7663,11 +7728,255 @@ async function resolveApiToken() {
|
|
|
7663
7728
|
if (stored) return stored;
|
|
7664
7729
|
throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
|
|
7665
7730
|
}
|
|
7731
|
+
|
|
7732
|
+
// src/git-info.ts
|
|
7733
|
+
var import_child_process5 = require("child_process");
|
|
7734
|
+
function resolveRepositoryRoot(cwd = process.cwd()) {
|
|
7735
|
+
try {
|
|
7736
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
7737
|
+
cwd,
|
|
7738
|
+
encoding: "utf-8"
|
|
7739
|
+
}).trim();
|
|
7740
|
+
} catch {
|
|
7741
|
+
throw new Error(`Not inside a git repository: ${cwd}`);
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
function resolveHeadCommit(repositoryRoot) {
|
|
7745
|
+
try {
|
|
7746
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
7747
|
+
cwd: repositoryRoot,
|
|
7748
|
+
encoding: "utf-8"
|
|
7749
|
+
}).trim();
|
|
7750
|
+
} catch {
|
|
7751
|
+
throw new Error(
|
|
7752
|
+
"Could not resolve the current commit from git (no commits yet, or not a git repository). Pass the commit explicitly with --commit <sha>."
|
|
7753
|
+
);
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
|
|
7757
|
+
// src/upload.ts
|
|
7758
|
+
var import_promises6 = require("fs/promises");
|
|
7759
|
+
var DEFAULT_RESULTS_API_BASE_URL = "https://api.codacy.com/2.0";
|
|
7760
|
+
function resolveResultsApiBaseUrl() {
|
|
7761
|
+
const raw = process.env.CODACY_RESULTS_API_BASE_URL || DEFAULT_RESULTS_API_BASE_URL;
|
|
7762
|
+
return raw.replace(/\/+$/, "");
|
|
7763
|
+
}
|
|
7764
|
+
function mapSarifLevelToCodacy(level) {
|
|
7765
|
+
switch (level) {
|
|
7766
|
+
case "error":
|
|
7767
|
+
return "Error";
|
|
7768
|
+
case "warning":
|
|
7769
|
+
return "Warning";
|
|
7770
|
+
case "note":
|
|
7771
|
+
return "Info";
|
|
7772
|
+
default:
|
|
7773
|
+
return "Warning";
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
function parseReport(fileContents) {
|
|
7777
|
+
let parsed;
|
|
7778
|
+
try {
|
|
7779
|
+
parsed = JSON.parse(fileContents);
|
|
7780
|
+
} catch (err) {
|
|
7781
|
+
throw new Error(
|
|
7782
|
+
`Report is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
7783
|
+
);
|
|
7784
|
+
}
|
|
7785
|
+
if (!parsed || typeof parsed !== "object") {
|
|
7786
|
+
throw new Error(
|
|
7787
|
+
"Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
|
|
7788
|
+
);
|
|
7789
|
+
}
|
|
7790
|
+
const obj = parsed;
|
|
7791
|
+
const looksSarif = Array.isArray(obj.runs) && (typeof obj.version === "string" && obj.version.startsWith("2.1") || typeof obj.$schema === "string" && obj.$schema.includes("sarif"));
|
|
7792
|
+
if (looksSarif) {
|
|
7793
|
+
return parseSarif(obj.runs);
|
|
7794
|
+
}
|
|
7795
|
+
if (Array.isArray(obj.issues)) {
|
|
7796
|
+
return parseAnalysisResult(obj);
|
|
7797
|
+
}
|
|
7798
|
+
throw new Error(
|
|
7799
|
+
"Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
|
|
7800
|
+
);
|
|
7801
|
+
}
|
|
7802
|
+
function parseSarif(runs) {
|
|
7803
|
+
const issues = [];
|
|
7804
|
+
for (const run of runs) {
|
|
7805
|
+
const toolId = run.tool?.driver?.name;
|
|
7806
|
+
if (!toolId) continue;
|
|
7807
|
+
for (const result of run.results ?? []) {
|
|
7808
|
+
const patternId = result.ruleId;
|
|
7809
|
+
if (!patternId) continue;
|
|
7810
|
+
const level = mapSarifLevelToCodacy(result.level);
|
|
7811
|
+
const message = result.message?.text ?? "";
|
|
7812
|
+
for (const loc of result.locations ?? []) {
|
|
7813
|
+
const filePath = loc.physicalLocation?.artifactLocation?.uri;
|
|
7814
|
+
const line = loc.physicalLocation?.region?.startLine;
|
|
7815
|
+
if (!filePath || !line) continue;
|
|
7816
|
+
issues.push({ toolId, patternId, filePath, line, message, level });
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
return issues;
|
|
7821
|
+
}
|
|
7822
|
+
function parseAnalysisResult(result) {
|
|
7823
|
+
return (result.issues ?? []).map((issue) => ({
|
|
7824
|
+
toolId: issue.toolId,
|
|
7825
|
+
patternId: issue.patternId,
|
|
7826
|
+
filePath: issue.filePath,
|
|
7827
|
+
line: issue.line,
|
|
7828
|
+
message: issue.message,
|
|
7829
|
+
// JSON carries the authoritative Codacy severity; pass it through unchanged.
|
|
7830
|
+
level: issue.severity
|
|
7831
|
+
}));
|
|
7832
|
+
}
|
|
7833
|
+
function buildPayloads(issues, resolveShortName) {
|
|
7834
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
7835
|
+
for (const issue of issues) {
|
|
7836
|
+
const list = byTool.get(issue.toolId);
|
|
7837
|
+
if (list) list.push(issue);
|
|
7838
|
+
else byTool.set(issue.toolId, [issue]);
|
|
7839
|
+
}
|
|
7840
|
+
const payloads = [];
|
|
7841
|
+
for (const [toolId, toolIssues] of byTool) {
|
|
7842
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
7843
|
+
for (const issue of toolIssues) {
|
|
7844
|
+
const entry = {
|
|
7845
|
+
Issue: {
|
|
7846
|
+
patternId: { value: issue.patternId },
|
|
7847
|
+
filename: issue.filePath,
|
|
7848
|
+
message: { text: issue.message },
|
|
7849
|
+
level: issue.level,
|
|
7850
|
+
location: { LineLocation: { line: issue.line } }
|
|
7851
|
+
}
|
|
7852
|
+
};
|
|
7853
|
+
const list = byFile.get(issue.filePath);
|
|
7854
|
+
if (list) list.push(entry);
|
|
7855
|
+
else byFile.set(issue.filePath, [entry]);
|
|
7856
|
+
}
|
|
7857
|
+
const results = [];
|
|
7858
|
+
for (const [filename, fileIssues] of byFile) {
|
|
7859
|
+
results.push({ filename, results: fileIssues });
|
|
7860
|
+
}
|
|
7861
|
+
payloads.push({
|
|
7862
|
+
tool: resolveShortName(toolId),
|
|
7863
|
+
issues: { Success: { results } }
|
|
7864
|
+
});
|
|
7865
|
+
}
|
|
7866
|
+
return payloads;
|
|
7867
|
+
}
|
|
7868
|
+
async function buildShortNameResolver(onProgress) {
|
|
7869
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7870
|
+
try {
|
|
7871
|
+
const tools = await listTools();
|
|
7872
|
+
for (const tool of tools) {
|
|
7873
|
+
if (tool.name && tool.shortName) byName.set(tool.name.toLowerCase(), tool.shortName);
|
|
7874
|
+
}
|
|
7875
|
+
} catch (err) {
|
|
7876
|
+
onProgress?.(
|
|
7877
|
+
`Warning: could not fetch Codacy tools list (${err instanceof Error ? err.message : String(err)}); falling back to tool ids as-is.`
|
|
7878
|
+
);
|
|
7879
|
+
}
|
|
7880
|
+
return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
|
|
7881
|
+
}
|
|
7882
|
+
var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7883
|
+
function assertValidCoordinate(kind, value) {
|
|
7884
|
+
if (!COORDINATE_PATTERN.test(value)) {
|
|
7885
|
+
throw new Error(
|
|
7886
|
+
`Invalid ${kind} "${value}": expected only letters, digits, and the characters . _ -`
|
|
7887
|
+
);
|
|
7888
|
+
}
|
|
7889
|
+
}
|
|
7890
|
+
function buildUrls(base, auth, commitUuid) {
|
|
7891
|
+
const commit = encodeURIComponent(commitUuid);
|
|
7892
|
+
if (auth.kind === "repository-token") {
|
|
7893
|
+
return {
|
|
7894
|
+
results: `${base}/commit/${commit}/issuesRemoteResults`,
|
|
7895
|
+
final: `${base}/commit/${commit}/resultsFinal`
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7898
|
+
assertValidCoordinate("provider", auth.provider);
|
|
7899
|
+
assertValidCoordinate("owner", auth.owner);
|
|
7900
|
+
assertValidCoordinate("repository", auth.repository);
|
|
7901
|
+
const p = encodeURIComponent(auth.provider);
|
|
7902
|
+
const o = encodeURIComponent(auth.owner);
|
|
7903
|
+
const r = encodeURIComponent(auth.repository);
|
|
7904
|
+
return {
|
|
7905
|
+
results: `${base}/${p}/${o}/${r}/commit/${commit}/issuesRemoteResults`,
|
|
7906
|
+
final: `${base}/${p}/${o}/${r}/commit/${commit}/resultsFinal`
|
|
7907
|
+
};
|
|
7908
|
+
}
|
|
7909
|
+
function authHeader(auth) {
|
|
7910
|
+
return auth.kind === "repository-token" ? { "project-token": auth.token } : { "api-token": auth.token };
|
|
7911
|
+
}
|
|
7912
|
+
async function uploadReport(options) {
|
|
7913
|
+
const { reportPath, commitUuid, auth, onProgress } = options;
|
|
7914
|
+
onProgress?.(`Reading report ${reportPath}...`);
|
|
7915
|
+
const contents = await (0, import_promises6.readFile)(reportPath, "utf-8");
|
|
7916
|
+
const issues = parseReport(contents);
|
|
7917
|
+
if (issues.length === 0) {
|
|
7918
|
+
onProgress?.("No issues found in the report; nothing to upload.");
|
|
7919
|
+
}
|
|
7920
|
+
const resolveShortName = await buildShortNameResolver(onProgress);
|
|
7921
|
+
const payloads = buildPayloads(issues, resolveShortName);
|
|
7922
|
+
const base = resolveResultsApiBaseUrl();
|
|
7923
|
+
const urls = buildUrls(base, auth, commitUuid);
|
|
7924
|
+
const headers = { "content-type": "application/json", ...authHeader(auth) };
|
|
7925
|
+
for (const payload of payloads) {
|
|
7926
|
+
const issuesInTool = payload.issues.Success.results.reduce(
|
|
7927
|
+
(sum, f) => sum + f.results.length,
|
|
7928
|
+
0
|
|
7929
|
+
);
|
|
7930
|
+
onProgress?.(`Uploading ${issuesInTool} issue(s) for ${payload.tool}...`);
|
|
7931
|
+
const res = await fetch(urls.results, {
|
|
7932
|
+
method: "POST",
|
|
7933
|
+
headers,
|
|
7934
|
+
// The body is a one-element array per tool, matching the Go CLI.
|
|
7935
|
+
body: JSON.stringify([payload])
|
|
7936
|
+
});
|
|
7937
|
+
if (!res.ok) {
|
|
7938
|
+
const body = await safeReadBody(res);
|
|
7939
|
+
throw new Error(
|
|
7940
|
+
`Failed to upload results for ${payload.tool}: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
|
|
7941
|
+
);
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
onProgress?.("Finalizing results...");
|
|
7945
|
+
let finalized = false;
|
|
7946
|
+
try {
|
|
7947
|
+
const res = await fetch(urls.final, { method: "POST", headers });
|
|
7948
|
+
finalized = res.ok;
|
|
7949
|
+
if (!res.ok) {
|
|
7950
|
+
const body = await safeReadBody(res);
|
|
7951
|
+
onProgress?.(
|
|
7952
|
+
`Warning: resultsFinal returned ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
|
|
7953
|
+
);
|
|
7954
|
+
}
|
|
7955
|
+
} catch (err) {
|
|
7956
|
+
onProgress?.(
|
|
7957
|
+
`Warning: resultsFinal request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7958
|
+
);
|
|
7959
|
+
}
|
|
7960
|
+
return {
|
|
7961
|
+
toolsUploaded: payloads.map((p) => p.tool),
|
|
7962
|
+
issueCount: issues.length,
|
|
7963
|
+
finalized
|
|
7964
|
+
};
|
|
7965
|
+
}
|
|
7966
|
+
async function safeReadBody(res) {
|
|
7967
|
+
try {
|
|
7968
|
+
const text = await res.text();
|
|
7969
|
+
return text.length > 500 ? text.slice(0, 500) + "\u2026" : text;
|
|
7970
|
+
} catch {
|
|
7971
|
+
return "";
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7666
7974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7667
7975
|
0 && (module.exports = {
|
|
7668
7976
|
analyze,
|
|
7669
7977
|
applyCodacyYamlExcludes,
|
|
7670
7978
|
applyConfigOperation,
|
|
7979
|
+
buildPayloads,
|
|
7671
7980
|
buildToolConfig,
|
|
7672
7981
|
clearAdapters,
|
|
7673
7982
|
computePatternSummary,
|
|
@@ -7699,6 +8008,7 @@ async function resolveApiToken() {
|
|
|
7699
8008
|
loadCredentials,
|
|
7700
8009
|
mergeConfigs,
|
|
7701
8010
|
parseAutoFilters,
|
|
8011
|
+
parseReport,
|
|
7702
8012
|
pruneEmptyTools,
|
|
7703
8013
|
readBaselineConfig,
|
|
7704
8014
|
readCodacyConfig,
|
|
@@ -7710,10 +8020,14 @@ async function resolveApiToken() {
|
|
|
7710
8020
|
resolveBaselinePath,
|
|
7711
8021
|
resolveConfigPath,
|
|
7712
8022
|
resolveEngineToAdapter,
|
|
8023
|
+
resolveHeadCommit,
|
|
7713
8024
|
resolveProxyConfig,
|
|
8025
|
+
resolveRepositoryRoot,
|
|
8026
|
+
resolveResultsApiBaseUrl,
|
|
7714
8027
|
rotateLogFiles,
|
|
7715
8028
|
saveCredentials,
|
|
7716
8029
|
updateConfigIncremental,
|
|
8030
|
+
uploadReport,
|
|
7717
8031
|
validateApiToken,
|
|
7718
8032
|
writeBaselineConfig,
|
|
7719
8033
|
writeCodacyConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/analysis-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.17.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.17.0"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|