@codacy/analysis-runner 0.14.0 → 0.16.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 +171 -1
- package/dist/index.js +379 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -853,4 +853,174 @@ declare function discover(repositoryRoot: string, options?: DiscoverOptions): Pr
|
|
|
853
853
|
*/
|
|
854
854
|
declare function getKnownDisplayNames(): Set<string>;
|
|
855
855
|
|
|
856
|
-
|
|
856
|
+
/**
|
|
857
|
+
* Git repository introspection helpers used by commands that need to know
|
|
858
|
+
* *where* they are (repository root) and *what* they are looking at (the current
|
|
859
|
+
* commit SHA). Kept separate from `git-scope.ts` (which resolves file *sets* for
|
|
860
|
+
* `--staged`/`--diff`/`--pr`); this module answers point questions about the repo.
|
|
861
|
+
*
|
|
862
|
+
* Both helpers shell out with `execFileSync` (never a shell string) so paths and
|
|
863
|
+
* refs can't be interpreted by the shell.
|
|
864
|
+
*/
|
|
865
|
+
/**
|
|
866
|
+
* Resolves the absolute path to the enclosing git repository's root.
|
|
867
|
+
*
|
|
868
|
+
* Wraps `git rev-parse --show-toplevel`. Throws a clear error when `cwd` is not
|
|
869
|
+
* inside a git working tree, so callers can surface an actionable message rather
|
|
870
|
+
* than a raw git failure.
|
|
871
|
+
*/
|
|
872
|
+
declare function resolveRepositoryRoot(cwd?: string): string;
|
|
873
|
+
/**
|
|
874
|
+
* Resolves the SHA of the current `HEAD` commit.
|
|
875
|
+
*
|
|
876
|
+
* Wraps `git rev-parse HEAD`. Used by `upload` to auto-detect the commit when the
|
|
877
|
+
* caller omits `--commit`. Throws when there is no commit yet (empty repo) or the
|
|
878
|
+
* directory is not a git repository, so the command can tell the user to pass
|
|
879
|
+
* `--commit` explicitly.
|
|
880
|
+
*/
|
|
881
|
+
declare function resolveHeadCommit(repositoryRoot: string): string;
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* `upload` — sends an analysis report produced by this CLI to Codacy, attaching
|
|
885
|
+
* its issues to a commit so they surface on Codacy Cloud.
|
|
886
|
+
*
|
|
887
|
+
* ## What it uploads
|
|
888
|
+
*
|
|
889
|
+
* Only reports this CLI produced, in either output format:
|
|
890
|
+
* - `sarif` — our SARIF v2.1.0 document, where `run.tool.driver.name` is the tool
|
|
891
|
+
* id and each `result.ruleId` is already a Codacy pattern id.
|
|
892
|
+
* - `json` — the full `AnalysisResult`, whose `issues[]` carry `toolId`, `patternId`,
|
|
893
|
+
* `severity`, etc.
|
|
894
|
+
*
|
|
895
|
+
* Because our reports already carry Codacy-native pattern ids, no online pattern
|
|
896
|
+
* lookup is needed (unlike the legacy Go CLI, which had to map raw tool rule ids to
|
|
897
|
+
* Codacy patterns). We normalize either format into a flat issue list and build the
|
|
898
|
+
* payload directly.
|
|
899
|
+
*
|
|
900
|
+
* ## Where it uploads
|
|
901
|
+
*
|
|
902
|
+
* The **legacy v2 API** (`https://api.codacy.com/2.0`), matching the Go CLI: one
|
|
903
|
+
* `issuesRemoteResults` POST per tool, then a single `resultsFinal` POST to close the
|
|
904
|
+
* commit. The backend stores the `issues` payload as an opaque blob and a separate
|
|
905
|
+
* downstream engine parses it, so the payload shape must match the Go CLI's exactly.
|
|
906
|
+
*
|
|
907
|
+
* ## Auth
|
|
908
|
+
*
|
|
909
|
+
* Two mutually exclusive modes (see {@link UploadAuth}): a repository token
|
|
910
|
+
* (`project-token` header, commit-scoped URL) or an account token (`api-token`
|
|
911
|
+
* header, provider/owner/repo-scoped URL).
|
|
912
|
+
*/
|
|
913
|
+
/**
|
|
914
|
+
* An issue flattened from either report format into the minimal fields the v2
|
|
915
|
+
* payload needs. `filePath` is relative to the repository root (both our SARIF and
|
|
916
|
+
* JSON emit repo-relative paths, so no recomputation is required).
|
|
917
|
+
*/
|
|
918
|
+
interface NormalizedIssue {
|
|
919
|
+
/** Tool id (our adapter id, e.g. "ESLint9"); becomes the payload's tool grouping. */
|
|
920
|
+
toolId: string;
|
|
921
|
+
/** Codacy pattern id (e.g. "ESLint9_accessor-pairs"). */
|
|
922
|
+
patternId: string;
|
|
923
|
+
/** Repository-relative file path. */
|
|
924
|
+
filePath: string;
|
|
925
|
+
/** 1-based line number. */
|
|
926
|
+
line: number;
|
|
927
|
+
/** Issue message text. */
|
|
928
|
+
message: string;
|
|
929
|
+
/** Codacy level string (Info/Warning/High/Error for json; Info/Warning/Error for sarif). */
|
|
930
|
+
level: string;
|
|
931
|
+
}
|
|
932
|
+
/** Authentication + routing for the upload. */
|
|
933
|
+
type UploadAuth = {
|
|
934
|
+
kind: "repository-token";
|
|
935
|
+
/** Repository (a.k.a. project) token; sent as the `project-token` header. */
|
|
936
|
+
token: string;
|
|
937
|
+
} | {
|
|
938
|
+
kind: "account-token";
|
|
939
|
+
/** Account/user token; sent as the `api-token` header. */
|
|
940
|
+
token: string;
|
|
941
|
+
provider: string;
|
|
942
|
+
owner: string;
|
|
943
|
+
repository: string;
|
|
944
|
+
};
|
|
945
|
+
/** Options for {@link uploadReport}. */
|
|
946
|
+
interface UploadOptions {
|
|
947
|
+
/** Absolute or CWD-relative path to the report file (sarif or json). */
|
|
948
|
+
reportPath: string;
|
|
949
|
+
/** Commit SHA/UUID the results attach to. */
|
|
950
|
+
commitUuid: string;
|
|
951
|
+
auth: UploadAuth;
|
|
952
|
+
/** Progress callback for human-facing messages (stderr in the CLI). */
|
|
953
|
+
onProgress?: (message: string) => void;
|
|
954
|
+
}
|
|
955
|
+
/** Outcome of an upload. */
|
|
956
|
+
interface UploadResult {
|
|
957
|
+
/** Tool short-names that had at least one issue uploaded. */
|
|
958
|
+
toolsUploaded: string[];
|
|
959
|
+
/** Total issues uploaded across all tools. */
|
|
960
|
+
issueCount: number;
|
|
961
|
+
/** Whether the final `resultsFinal` call succeeded. */
|
|
962
|
+
finalized: boolean;
|
|
963
|
+
}
|
|
964
|
+
/** The per-tool payload object (wrapped in a one-element array per request). */
|
|
965
|
+
interface ToolPayload {
|
|
966
|
+
tool: string;
|
|
967
|
+
issues: {
|
|
968
|
+
Success: {
|
|
969
|
+
results: FileResults[];
|
|
970
|
+
};
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
interface FileResults {
|
|
974
|
+
filename: string;
|
|
975
|
+
results: Array<{
|
|
976
|
+
Issue: PayloadIssue;
|
|
977
|
+
}>;
|
|
978
|
+
}
|
|
979
|
+
interface PayloadIssue {
|
|
980
|
+
patternId: {
|
|
981
|
+
value: string;
|
|
982
|
+
};
|
|
983
|
+
filename: string;
|
|
984
|
+
message: {
|
|
985
|
+
text: string;
|
|
986
|
+
};
|
|
987
|
+
level: string;
|
|
988
|
+
location: {
|
|
989
|
+
LineLocation: {
|
|
990
|
+
line: number;
|
|
991
|
+
};
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Resolves the base URL for the legacy v2 results API. Defaults to Codacy SaaS;
|
|
996
|
+
* override with `CODACY_RESULTS_API_BASE_URL` for self-hosted deployments. Note this
|
|
997
|
+
* is a different host from `CODACY_API_BASE_URL` (the v3 app API).
|
|
998
|
+
*/
|
|
999
|
+
declare function resolveResultsApiBaseUrl(): string;
|
|
1000
|
+
/**
|
|
1001
|
+
* Detects the report format and flattens it into {@link NormalizedIssue}s.
|
|
1002
|
+
* Accepts only reports produced by this CLI (our SARIF or our JSON `AnalysisResult`);
|
|
1003
|
+
* throws a clear error otherwise.
|
|
1004
|
+
*/
|
|
1005
|
+
declare function parseReport(fileContents: string): NormalizedIssue[];
|
|
1006
|
+
/**
|
|
1007
|
+
* Builds one v2 payload per tool, grouping issues by file. `resolveShortName` maps
|
|
1008
|
+
* our internal tool id to the Codacy tool short-name expected in the `tool` field
|
|
1009
|
+
* (which becomes an S3 key segment consumed downstream).
|
|
1010
|
+
*/
|
|
1011
|
+
declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string): ToolPayload[];
|
|
1012
|
+
/**
|
|
1013
|
+
* Uploads an analysis report to Codacy. Reads and parses the report, resolves tool
|
|
1014
|
+
* short-names, POSTs one `issuesRemoteResults` per tool, then a single `resultsFinal`.
|
|
1015
|
+
*
|
|
1016
|
+
* Throws on a failed `issuesRemoteResults` POST (partial uploads shouldn't be
|
|
1017
|
+
* silently finalized). A failed `resultsFinal` is surfaced via `onProgress` and
|
|
1018
|
+
* reflected in the returned {@link UploadResult}, matching the Go CLI's non-fatal
|
|
1019
|
+
* treatment of finalization.
|
|
1020
|
+
*
|
|
1021
|
+
* Proxy/TLS is honored automatically — all `fetch` traffic goes through the global
|
|
1022
|
+
* dispatcher installed by `configureProxy()`.
|
|
1023
|
+
*/
|
|
1024
|
+
declare function uploadReport(options: UploadOptions): Promise<UploadResult>;
|
|
1025
|
+
|
|
1026
|
+
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,
|
|
@@ -89,6 +95,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
89
95
|
// src/analyze.ts
|
|
90
96
|
var import_os2 = __toESM(require("os"));
|
|
91
97
|
var import_path4 = __toESM(require("path"));
|
|
98
|
+
var import_fs3 = __toESM(require("fs"));
|
|
92
99
|
var import_child_process3 = require("child_process");
|
|
93
100
|
var import_util = require("util");
|
|
94
101
|
var import_tooling3 = require("@codacy/tooling");
|
|
@@ -586,7 +593,7 @@ function routeFilesToTool(files, adapter, toolConfig) {
|
|
|
586
593
|
}
|
|
587
594
|
function filterByTargetFiles(allFiles, targetPatterns) {
|
|
588
595
|
if (targetPatterns.length === 0) return allFiles;
|
|
589
|
-
const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
|
|
596
|
+
const normalized = targetPatterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
590
597
|
const exact = new Set(normalized);
|
|
591
598
|
const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
|
|
592
599
|
const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
|
|
@@ -1053,6 +1060,26 @@ async function analyze(options) {
|
|
|
1053
1060
|
[]
|
|
1054
1061
|
);
|
|
1055
1062
|
}
|
|
1063
|
+
const rootStat = import_fs3.default.statSync(options.repositoryRoot, { throwIfNoEntry: false });
|
|
1064
|
+
if (!rootStat?.isDirectory()) {
|
|
1065
|
+
return buildResult(
|
|
1066
|
+
startedAt,
|
|
1067
|
+
options.repositoryRoot,
|
|
1068
|
+
mode,
|
|
1069
|
+
emptyCapability,
|
|
1070
|
+
[],
|
|
1071
|
+
[
|
|
1072
|
+
{
|
|
1073
|
+
toolId: "runner",
|
|
1074
|
+
phase: "requirementCheck",
|
|
1075
|
+
kind: "RepositoryNotFound",
|
|
1076
|
+
message: `${options.repositoryRoot} does not exist or is not a directory. Pass a path to an existing repository, or a quoted glob (e.g. "src/**/*.ts") to scope files.`,
|
|
1077
|
+
level: "error"
|
|
1078
|
+
}
|
|
1079
|
+
],
|
|
1080
|
+
[]
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1056
1083
|
try {
|
|
1057
1084
|
await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
1058
1085
|
cwd: options.repositoryRoot
|
|
@@ -2604,11 +2631,24 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2604
2631
|
// --- Scala — Frameworks ---
|
|
2605
2632
|
["play-server", { displayName: "Play Framework", kind: "framework" }],
|
|
2606
2633
|
["akka-actor", { displayName: "Akka", kind: "framework" }],
|
|
2634
|
+
["spray-can", { displayName: "Spray", kind: "framework" }],
|
|
2607
2635
|
// --- Scala — Libraries ---
|
|
2608
2636
|
["scalatest", { displayName: "ScalaTest", kind: "library" }],
|
|
2609
2637
|
["specs2-core", { displayName: "Specs2", kind: "library" }],
|
|
2610
2638
|
["cats-core", { displayName: "Cats", kind: "library" }],
|
|
2611
2639
|
["circe-core", { displayName: "Circe", kind: "library" }],
|
|
2640
|
+
// --- Elixir — Frameworks ---
|
|
2641
|
+
["phoenix", { displayName: "Phoenix", kind: "framework" }],
|
|
2642
|
+
["plug", { displayName: "Plug", kind: "framework" }],
|
|
2643
|
+
["plug_cowboy", { displayName: "Plug", kind: "framework" }],
|
|
2644
|
+
// --- Elixir — Libraries ---
|
|
2645
|
+
["ecto", { displayName: "Ecto", kind: "library" }],
|
|
2646
|
+
["ecto_sql", { displayName: "Ecto", kind: "library" }],
|
|
2647
|
+
["phoenix_ecto", { displayName: "Ecto", kind: "library" }],
|
|
2648
|
+
// --- Crystal — Frameworks ---
|
|
2649
|
+
["amber", { displayName: "Amber", kind: "framework" }],
|
|
2650
|
+
["lucky", { displayName: "Lucky", kind: "framework" }],
|
|
2651
|
+
["kemal", { displayName: "Kemal", kind: "framework" }],
|
|
2612
2652
|
// --- Dart — Frameworks ---
|
|
2613
2653
|
// `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
|
|
2614
2654
|
// here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
|
|
@@ -2746,6 +2786,31 @@ var FILE_SIGNAL_RULES = [
|
|
|
2746
2786
|
name: "suitecloud.config.js",
|
|
2747
2787
|
matches: (f) => pathBasename(f) === "suitecloud.config.js",
|
|
2748
2788
|
classification: { displayName: "SuiteScript", kind: "platform" }
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
name: ".github/workflows",
|
|
2792
|
+
matches: (f) => f.startsWith(".github/workflows/"),
|
|
2793
|
+
classification: { displayName: "GitHub Actions", kind: "framework" }
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
name: ".circleci",
|
|
2797
|
+
matches: (f) => f.startsWith(".circleci/"),
|
|
2798
|
+
classification: { displayName: "CircleCI", kind: "framework" }
|
|
2799
|
+
},
|
|
2800
|
+
{
|
|
2801
|
+
name: ".gitlab-ci.yml",
|
|
2802
|
+
matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
|
|
2803
|
+
classification: { displayName: "GitLab CI", kind: "framework" }
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
name: "bitbucket-pipelines.yml",
|
|
2807
|
+
matches: (f) => f === "bitbucket-pipelines.yml",
|
|
2808
|
+
classification: { displayName: "Bitbucket Pipelines", kind: "framework" }
|
|
2809
|
+
},
|
|
2810
|
+
{
|
|
2811
|
+
name: "azure-pipelines.yml",
|
|
2812
|
+
matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
|
|
2813
|
+
classification: { displayName: "Azure Pipelines", kind: "framework" }
|
|
2749
2814
|
}
|
|
2750
2815
|
];
|
|
2751
2816
|
function detectFileSignals(files) {
|
|
@@ -2991,6 +3056,15 @@ function parseGemfile(content) {
|
|
|
2991
3056
|
}
|
|
2992
3057
|
return deps;
|
|
2993
3058
|
}
|
|
3059
|
+
function parseMixExs(content) {
|
|
3060
|
+
const deps = [];
|
|
3061
|
+
const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
|
|
3062
|
+
let match;
|
|
3063
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
3064
|
+
deps.push({ name: match[1], version: match[2] || void 0 });
|
|
3065
|
+
}
|
|
3066
|
+
return deps;
|
|
3067
|
+
}
|
|
2994
3068
|
function parseGoMod(content) {
|
|
2995
3069
|
const deps = [];
|
|
2996
3070
|
const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
|
|
@@ -3030,6 +3104,15 @@ function parseBuildGradle(content) {
|
|
|
3030
3104
|
}
|
|
3031
3105
|
return deps;
|
|
3032
3106
|
}
|
|
3107
|
+
function parseBuildSbt(content) {
|
|
3108
|
+
const deps = [];
|
|
3109
|
+
const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%\s*"([^"]+)"/g;
|
|
3110
|
+
let match;
|
|
3111
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
3112
|
+
deps.push({ name: match[2], version: match[3] });
|
|
3113
|
+
}
|
|
3114
|
+
return deps;
|
|
3115
|
+
}
|
|
3033
3116
|
function parseCargoToml(content) {
|
|
3034
3117
|
const deps = [];
|
|
3035
3118
|
let inDepsSection = false;
|
|
@@ -3110,6 +3193,41 @@ function parsePubspecYaml(content) {
|
|
|
3110
3193
|
}
|
|
3111
3194
|
return deps;
|
|
3112
3195
|
}
|
|
3196
|
+
function parseShardYml(content) {
|
|
3197
|
+
const deps = [];
|
|
3198
|
+
let inSection = false;
|
|
3199
|
+
let depIndent = -1;
|
|
3200
|
+
let current;
|
|
3201
|
+
const flush = () => {
|
|
3202
|
+
if (current) deps.push(current);
|
|
3203
|
+
current = void 0;
|
|
3204
|
+
};
|
|
3205
|
+
for (const rawLine of content.split("\n")) {
|
|
3206
|
+
const trimmed = rawLine.trim();
|
|
3207
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
3208
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
3209
|
+
if (indent === 0) {
|
|
3210
|
+
flush();
|
|
3211
|
+
inSection = /^(dependencies|development_dependencies):/.test(trimmed);
|
|
3212
|
+
depIndent = -1;
|
|
3213
|
+
continue;
|
|
3214
|
+
}
|
|
3215
|
+
if (!inSection) continue;
|
|
3216
|
+
if (depIndent === -1) depIndent = indent;
|
|
3217
|
+
if (indent === depIndent) {
|
|
3218
|
+
flush();
|
|
3219
|
+
const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
|
|
3220
|
+
current = m ? { name: m[1] } : void 0;
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
if (indent > depIndent && current) {
|
|
3224
|
+
const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
|
|
3225
|
+
if (v) current.version = v[1];
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
flush();
|
|
3229
|
+
return deps;
|
|
3230
|
+
}
|
|
3113
3231
|
function parseXmlDependencyElements(content, elementRegex, nameAttr) {
|
|
3114
3232
|
const deps = [];
|
|
3115
3233
|
const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
@@ -3144,11 +3262,14 @@ var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
|
|
|
3144
3262
|
["pom.xml", parsePomXml],
|
|
3145
3263
|
["build.gradle", parseBuildGradle],
|
|
3146
3264
|
["build.gradle.kts", parseBuildGradle],
|
|
3265
|
+
["build.sbt", parseBuildSbt],
|
|
3147
3266
|
["Cargo.toml", parseCargoToml],
|
|
3148
3267
|
["composer.json", parseComposerJson],
|
|
3149
3268
|
["Podfile", parsePodfile],
|
|
3150
3269
|
["Package.swift", parsePackageSwift],
|
|
3151
3270
|
["pubspec.yaml", parsePubspecYaml],
|
|
3271
|
+
["mix.exs", parseMixExs],
|
|
3272
|
+
["shard.yml", parseShardYml],
|
|
3152
3273
|
["packages.config", parsePackagesConfig],
|
|
3153
3274
|
["Directory.Packages.props", parseCsproj]
|
|
3154
3275
|
]);
|
|
@@ -7197,6 +7318,11 @@ async function initContainerConfig(repositoryRoot, adapters) {
|
|
|
7197
7318
|
}
|
|
7198
7319
|
return createCodacyConfig({ source: "container", tools, includeFiles });
|
|
7199
7320
|
}
|
|
7321
|
+
function hasMatchingFiles(files, filePatterns) {
|
|
7322
|
+
const matcher = new CompiledMatcher(filePatterns);
|
|
7323
|
+
if (matcher.matchAll) return files.length > 0;
|
|
7324
|
+
return files.some((file) => matcher.matches(file, file.split("/").pop() ?? ""));
|
|
7325
|
+
}
|
|
7200
7326
|
async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
|
|
7201
7327
|
const discoveryResult = await discover(repositoryRoot);
|
|
7202
7328
|
onDiscoveryComplete?.(discoveryResult);
|
|
@@ -7206,6 +7332,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7206
7332
|
for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
|
|
7207
7333
|
const repoLanguages = new Set(discoveryResult.languages);
|
|
7208
7334
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
7335
|
+
const repoFiles = listRepoFiles(repositoryRoot);
|
|
7209
7336
|
const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
|
|
7210
7337
|
const candidates = [];
|
|
7211
7338
|
for (const adapter of adapters) {
|
|
@@ -7213,6 +7340,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7213
7340
|
const hasOverlap = adapter.languages.some((lang) => repoLanguages.has(lang));
|
|
7214
7341
|
if (!hasOverlap) continue;
|
|
7215
7342
|
}
|
|
7343
|
+
if (!hasMatchingFiles(repoFiles, adapter.filePatterns)) continue;
|
|
7216
7344
|
candidates.push(adapter);
|
|
7217
7345
|
}
|
|
7218
7346
|
const deduped = deduplicateToolFamilies(candidates, localConfigs);
|
|
@@ -7256,6 +7384,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7256
7384
|
}
|
|
7257
7385
|
const family = toolFamily(desc.id);
|
|
7258
7386
|
if (selectedFamilies.has(family)) continue;
|
|
7387
|
+
if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
|
|
7259
7388
|
const patternsMetadata = loadUnsupportedPatterns(desc.id);
|
|
7260
7389
|
if (patternsMetadata.length === 0) continue;
|
|
7261
7390
|
const selectedPatterns = filterAutoPatterns(
|
|
@@ -7540,11 +7669,255 @@ async function resolveApiToken() {
|
|
|
7540
7669
|
if (stored) return stored;
|
|
7541
7670
|
throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
|
|
7542
7671
|
}
|
|
7672
|
+
|
|
7673
|
+
// src/git-info.ts
|
|
7674
|
+
var import_child_process5 = require("child_process");
|
|
7675
|
+
function resolveRepositoryRoot(cwd = process.cwd()) {
|
|
7676
|
+
try {
|
|
7677
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
7678
|
+
cwd,
|
|
7679
|
+
encoding: "utf-8"
|
|
7680
|
+
}).trim();
|
|
7681
|
+
} catch {
|
|
7682
|
+
throw new Error(`Not inside a git repository: ${cwd}`);
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
function resolveHeadCommit(repositoryRoot) {
|
|
7686
|
+
try {
|
|
7687
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
7688
|
+
cwd: repositoryRoot,
|
|
7689
|
+
encoding: "utf-8"
|
|
7690
|
+
}).trim();
|
|
7691
|
+
} catch {
|
|
7692
|
+
throw new Error(
|
|
7693
|
+
"Could not resolve the current commit from git (no commits yet, or not a git repository). Pass the commit explicitly with --commit <sha>."
|
|
7694
|
+
);
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7698
|
+
// src/upload.ts
|
|
7699
|
+
var import_promises6 = require("fs/promises");
|
|
7700
|
+
var DEFAULT_RESULTS_API_BASE_URL = "https://api.codacy.com/2.0";
|
|
7701
|
+
function resolveResultsApiBaseUrl() {
|
|
7702
|
+
const raw = process.env.CODACY_RESULTS_API_BASE_URL || DEFAULT_RESULTS_API_BASE_URL;
|
|
7703
|
+
return raw.replace(/\/+$/, "");
|
|
7704
|
+
}
|
|
7705
|
+
function mapSarifLevelToCodacy(level) {
|
|
7706
|
+
switch (level) {
|
|
7707
|
+
case "error":
|
|
7708
|
+
return "Error";
|
|
7709
|
+
case "warning":
|
|
7710
|
+
return "Warning";
|
|
7711
|
+
case "note":
|
|
7712
|
+
return "Info";
|
|
7713
|
+
default:
|
|
7714
|
+
return "Warning";
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
function parseReport(fileContents) {
|
|
7718
|
+
let parsed;
|
|
7719
|
+
try {
|
|
7720
|
+
parsed = JSON.parse(fileContents);
|
|
7721
|
+
} catch (err) {
|
|
7722
|
+
throw new Error(
|
|
7723
|
+
`Report is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
7724
|
+
);
|
|
7725
|
+
}
|
|
7726
|
+
if (!parsed || typeof parsed !== "object") {
|
|
7727
|
+
throw new Error(
|
|
7728
|
+
"Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
|
|
7729
|
+
);
|
|
7730
|
+
}
|
|
7731
|
+
const obj = parsed;
|
|
7732
|
+
const looksSarif = Array.isArray(obj.runs) && (typeof obj.version === "string" && obj.version.startsWith("2.1") || typeof obj.$schema === "string" && obj.$schema.includes("sarif"));
|
|
7733
|
+
if (looksSarif) {
|
|
7734
|
+
return parseSarif(obj.runs);
|
|
7735
|
+
}
|
|
7736
|
+
if (Array.isArray(obj.issues)) {
|
|
7737
|
+
return parseAnalysisResult(obj);
|
|
7738
|
+
}
|
|
7739
|
+
throw new Error(
|
|
7740
|
+
"Unrecognized report format; expected SARIF or JSON produced by `codacy-analysis analyze`."
|
|
7741
|
+
);
|
|
7742
|
+
}
|
|
7743
|
+
function parseSarif(runs) {
|
|
7744
|
+
const issues = [];
|
|
7745
|
+
for (const run of runs) {
|
|
7746
|
+
const toolId = run.tool?.driver?.name;
|
|
7747
|
+
if (!toolId) continue;
|
|
7748
|
+
for (const result of run.results ?? []) {
|
|
7749
|
+
const patternId = result.ruleId;
|
|
7750
|
+
if (!patternId) continue;
|
|
7751
|
+
const level = mapSarifLevelToCodacy(result.level);
|
|
7752
|
+
const message = result.message?.text ?? "";
|
|
7753
|
+
for (const loc of result.locations ?? []) {
|
|
7754
|
+
const filePath = loc.physicalLocation?.artifactLocation?.uri;
|
|
7755
|
+
const line = loc.physicalLocation?.region?.startLine;
|
|
7756
|
+
if (!filePath || !line) continue;
|
|
7757
|
+
issues.push({ toolId, patternId, filePath, line, message, level });
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
return issues;
|
|
7762
|
+
}
|
|
7763
|
+
function parseAnalysisResult(result) {
|
|
7764
|
+
return (result.issues ?? []).map((issue) => ({
|
|
7765
|
+
toolId: issue.toolId,
|
|
7766
|
+
patternId: issue.patternId,
|
|
7767
|
+
filePath: issue.filePath,
|
|
7768
|
+
line: issue.line,
|
|
7769
|
+
message: issue.message,
|
|
7770
|
+
// JSON carries the authoritative Codacy severity; pass it through unchanged.
|
|
7771
|
+
level: issue.severity
|
|
7772
|
+
}));
|
|
7773
|
+
}
|
|
7774
|
+
function buildPayloads(issues, resolveShortName) {
|
|
7775
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
7776
|
+
for (const issue of issues) {
|
|
7777
|
+
const list = byTool.get(issue.toolId);
|
|
7778
|
+
if (list) list.push(issue);
|
|
7779
|
+
else byTool.set(issue.toolId, [issue]);
|
|
7780
|
+
}
|
|
7781
|
+
const payloads = [];
|
|
7782
|
+
for (const [toolId, toolIssues] of byTool) {
|
|
7783
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
7784
|
+
for (const issue of toolIssues) {
|
|
7785
|
+
const entry = {
|
|
7786
|
+
Issue: {
|
|
7787
|
+
patternId: { value: issue.patternId },
|
|
7788
|
+
filename: issue.filePath,
|
|
7789
|
+
message: { text: issue.message },
|
|
7790
|
+
level: issue.level,
|
|
7791
|
+
location: { LineLocation: { line: issue.line } }
|
|
7792
|
+
}
|
|
7793
|
+
};
|
|
7794
|
+
const list = byFile.get(issue.filePath);
|
|
7795
|
+
if (list) list.push(entry);
|
|
7796
|
+
else byFile.set(issue.filePath, [entry]);
|
|
7797
|
+
}
|
|
7798
|
+
const results = [];
|
|
7799
|
+
for (const [filename, fileIssues] of byFile) {
|
|
7800
|
+
results.push({ filename, results: fileIssues });
|
|
7801
|
+
}
|
|
7802
|
+
payloads.push({
|
|
7803
|
+
tool: resolveShortName(toolId),
|
|
7804
|
+
issues: { Success: { results } }
|
|
7805
|
+
});
|
|
7806
|
+
}
|
|
7807
|
+
return payloads;
|
|
7808
|
+
}
|
|
7809
|
+
async function buildShortNameResolver(onProgress) {
|
|
7810
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7811
|
+
try {
|
|
7812
|
+
const tools = await listTools();
|
|
7813
|
+
for (const tool of tools) {
|
|
7814
|
+
if (tool.name && tool.shortName) byName.set(tool.name.toLowerCase(), tool.shortName);
|
|
7815
|
+
}
|
|
7816
|
+
} catch (err) {
|
|
7817
|
+
onProgress?.(
|
|
7818
|
+
`Warning: could not fetch Codacy tools list (${err instanceof Error ? err.message : String(err)}); falling back to tool ids as-is.`
|
|
7819
|
+
);
|
|
7820
|
+
}
|
|
7821
|
+
return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
|
|
7822
|
+
}
|
|
7823
|
+
var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7824
|
+
function assertValidCoordinate(kind, value) {
|
|
7825
|
+
if (!COORDINATE_PATTERN.test(value)) {
|
|
7826
|
+
throw new Error(
|
|
7827
|
+
`Invalid ${kind} "${value}": expected only letters, digits, and the characters . _ -`
|
|
7828
|
+
);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
function buildUrls(base, auth, commitUuid) {
|
|
7832
|
+
const commit = encodeURIComponent(commitUuid);
|
|
7833
|
+
if (auth.kind === "repository-token") {
|
|
7834
|
+
return {
|
|
7835
|
+
results: `${base}/commit/${commit}/issuesRemoteResults`,
|
|
7836
|
+
final: `${base}/commit/${commit}/resultsFinal`
|
|
7837
|
+
};
|
|
7838
|
+
}
|
|
7839
|
+
assertValidCoordinate("provider", auth.provider);
|
|
7840
|
+
assertValidCoordinate("owner", auth.owner);
|
|
7841
|
+
assertValidCoordinate("repository", auth.repository);
|
|
7842
|
+
const p = encodeURIComponent(auth.provider);
|
|
7843
|
+
const o = encodeURIComponent(auth.owner);
|
|
7844
|
+
const r = encodeURIComponent(auth.repository);
|
|
7845
|
+
return {
|
|
7846
|
+
results: `${base}/${p}/${o}/${r}/commit/${commit}/issuesRemoteResults`,
|
|
7847
|
+
final: `${base}/${p}/${o}/${r}/commit/${commit}/resultsFinal`
|
|
7848
|
+
};
|
|
7849
|
+
}
|
|
7850
|
+
function authHeader(auth) {
|
|
7851
|
+
return auth.kind === "repository-token" ? { "project-token": auth.token } : { "api-token": auth.token };
|
|
7852
|
+
}
|
|
7853
|
+
async function uploadReport(options) {
|
|
7854
|
+
const { reportPath, commitUuid, auth, onProgress } = options;
|
|
7855
|
+
onProgress?.(`Reading report ${reportPath}...`);
|
|
7856
|
+
const contents = await (0, import_promises6.readFile)(reportPath, "utf-8");
|
|
7857
|
+
const issues = parseReport(contents);
|
|
7858
|
+
if (issues.length === 0) {
|
|
7859
|
+
onProgress?.("No issues found in the report; nothing to upload.");
|
|
7860
|
+
}
|
|
7861
|
+
const resolveShortName = await buildShortNameResolver(onProgress);
|
|
7862
|
+
const payloads = buildPayloads(issues, resolveShortName);
|
|
7863
|
+
const base = resolveResultsApiBaseUrl();
|
|
7864
|
+
const urls = buildUrls(base, auth, commitUuid);
|
|
7865
|
+
const headers = { "content-type": "application/json", ...authHeader(auth) };
|
|
7866
|
+
for (const payload of payloads) {
|
|
7867
|
+
const issuesInTool = payload.issues.Success.results.reduce(
|
|
7868
|
+
(sum, f) => sum + f.results.length,
|
|
7869
|
+
0
|
|
7870
|
+
);
|
|
7871
|
+
onProgress?.(`Uploading ${issuesInTool} issue(s) for ${payload.tool}...`);
|
|
7872
|
+
const res = await fetch(urls.results, {
|
|
7873
|
+
method: "POST",
|
|
7874
|
+
headers,
|
|
7875
|
+
// The body is a one-element array per tool, matching the Go CLI.
|
|
7876
|
+
body: JSON.stringify([payload])
|
|
7877
|
+
});
|
|
7878
|
+
if (!res.ok) {
|
|
7879
|
+
const body = await safeReadBody(res);
|
|
7880
|
+
throw new Error(
|
|
7881
|
+
`Failed to upload results for ${payload.tool}: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
|
|
7882
|
+
);
|
|
7883
|
+
}
|
|
7884
|
+
}
|
|
7885
|
+
onProgress?.("Finalizing results...");
|
|
7886
|
+
let finalized = false;
|
|
7887
|
+
try {
|
|
7888
|
+
const res = await fetch(urls.final, { method: "POST", headers });
|
|
7889
|
+
finalized = res.ok;
|
|
7890
|
+
if (!res.ok) {
|
|
7891
|
+
const body = await safeReadBody(res);
|
|
7892
|
+
onProgress?.(
|
|
7893
|
+
`Warning: resultsFinal returned ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
|
|
7894
|
+
);
|
|
7895
|
+
}
|
|
7896
|
+
} catch (err) {
|
|
7897
|
+
onProgress?.(
|
|
7898
|
+
`Warning: resultsFinal request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7899
|
+
);
|
|
7900
|
+
}
|
|
7901
|
+
return {
|
|
7902
|
+
toolsUploaded: payloads.map((p) => p.tool),
|
|
7903
|
+
issueCount: issues.length,
|
|
7904
|
+
finalized
|
|
7905
|
+
};
|
|
7906
|
+
}
|
|
7907
|
+
async function safeReadBody(res) {
|
|
7908
|
+
try {
|
|
7909
|
+
const text = await res.text();
|
|
7910
|
+
return text.length > 500 ? text.slice(0, 500) + "\u2026" : text;
|
|
7911
|
+
} catch {
|
|
7912
|
+
return "";
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7543
7915
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7544
7916
|
0 && (module.exports = {
|
|
7545
7917
|
analyze,
|
|
7546
7918
|
applyCodacyYamlExcludes,
|
|
7547
7919
|
applyConfigOperation,
|
|
7920
|
+
buildPayloads,
|
|
7548
7921
|
buildToolConfig,
|
|
7549
7922
|
clearAdapters,
|
|
7550
7923
|
computePatternSummary,
|
|
@@ -7576,6 +7949,7 @@ async function resolveApiToken() {
|
|
|
7576
7949
|
loadCredentials,
|
|
7577
7950
|
mergeConfigs,
|
|
7578
7951
|
parseAutoFilters,
|
|
7952
|
+
parseReport,
|
|
7579
7953
|
pruneEmptyTools,
|
|
7580
7954
|
readBaselineConfig,
|
|
7581
7955
|
readCodacyConfig,
|
|
@@ -7587,10 +7961,14 @@ async function resolveApiToken() {
|
|
|
7587
7961
|
resolveBaselinePath,
|
|
7588
7962
|
resolveConfigPath,
|
|
7589
7963
|
resolveEngineToAdapter,
|
|
7964
|
+
resolveHeadCommit,
|
|
7590
7965
|
resolveProxyConfig,
|
|
7966
|
+
resolveRepositoryRoot,
|
|
7967
|
+
resolveResultsApiBaseUrl,
|
|
7591
7968
|
rotateLogFiles,
|
|
7592
7969
|
saveCredentials,
|
|
7593
7970
|
updateConfigIncremental,
|
|
7971
|
+
uploadReport,
|
|
7594
7972
|
validateApiToken,
|
|
7595
7973
|
writeBaselineConfig,
|
|
7596
7974
|
writeCodacyConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/analysis-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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.16.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.16.0"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|