opencode-sonarqube 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +356 -247
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3999,47 +3999,32 @@ function numberToRating(num) {
|
|
|
3999
3999
|
const ratings = ["A", "B", "C", "D", "E"];
|
|
4000
4000
|
return ratings[num - 1] ?? "?";
|
|
4001
4001
|
}
|
|
4002
|
-
function
|
|
4002
|
+
function createSonarQubeError(name, message, code, statusCode) {
|
|
4003
4003
|
const error45 = new Error(message);
|
|
4004
|
-
error45.name =
|
|
4004
|
+
error45.name = name;
|
|
4005
4005
|
error45.code = code;
|
|
4006
4006
|
error45.statusCode = statusCode;
|
|
4007
4007
|
return error45;
|
|
4008
4008
|
}
|
|
4009
|
+
function SonarQubeError(message, code, statusCode) {
|
|
4010
|
+
return createSonarQubeError("SonarQubeError", message, code, statusCode);
|
|
4011
|
+
}
|
|
4009
4012
|
function ConnectionError(message) {
|
|
4010
|
-
|
|
4011
|
-
error45.name = "ConnectionError";
|
|
4012
|
-
error45.code = "CONNECTION_ERROR";
|
|
4013
|
-
return error45;
|
|
4013
|
+
return createSonarQubeError("ConnectionError", message, "CONNECTION_ERROR");
|
|
4014
4014
|
}
|
|
4015
4015
|
function AuthenticationError(message) {
|
|
4016
|
-
|
|
4017
|
-
error45.name = "AuthenticationError";
|
|
4018
|
-
error45.code = "AUTH_ERROR";
|
|
4019
|
-
error45.statusCode = 401;
|
|
4020
|
-
return error45;
|
|
4016
|
+
return createSonarQubeError("AuthenticationError", message, "AUTH_ERROR", 401);
|
|
4021
4017
|
}
|
|
4022
4018
|
function ProjectNotFoundError(projectKey) {
|
|
4023
|
-
|
|
4024
|
-
error45.name = "ProjectNotFoundError";
|
|
4025
|
-
error45.code = "PROJECT_NOT_FOUND";
|
|
4026
|
-
error45.statusCode = 404;
|
|
4027
|
-
return error45;
|
|
4019
|
+
return createSonarQubeError("ProjectNotFoundError", `Project '${projectKey}' not found`, "PROJECT_NOT_FOUND", 404);
|
|
4028
4020
|
}
|
|
4029
4021
|
function RateLimitError(retryAfter) {
|
|
4030
4022
|
const hasRetryAfter = retryAfter !== undefined && retryAfter > 0;
|
|
4031
4023
|
const retryMessage = hasRetryAfter ? `. Retry after ${retryAfter}s` : "";
|
|
4032
|
-
|
|
4033
|
-
error45.name = "RateLimitError";
|
|
4034
|
-
error45.code = "RATE_LIMIT";
|
|
4035
|
-
error45.statusCode = 429;
|
|
4036
|
-
return error45;
|
|
4024
|
+
return createSonarQubeError("RateLimitError", `Rate limit exceeded${retryMessage}`, "RATE_LIMIT", 429);
|
|
4037
4025
|
}
|
|
4038
4026
|
function SetupError(message) {
|
|
4039
|
-
|
|
4040
|
-
error45.name = "SetupError";
|
|
4041
|
-
error45.code = "SETUP_ERROR";
|
|
4042
|
-
return error45;
|
|
4027
|
+
return createSonarQubeError("SetupError", message, "SETUP_ERROR");
|
|
4043
4028
|
}
|
|
4044
4029
|
var SonarQubeConfigSchema, ProjectStateSchema, ENTERPRISE_THRESHOLDS, STANDARD_THRESHOLDS, RELAXED_THRESHOLDS;
|
|
4045
4030
|
var init_types2 = __esm(() => {
|
|
@@ -4409,31 +4394,14 @@ async function parsePackageJson(directory) {
|
|
|
4409
4394
|
}
|
|
4410
4395
|
function detectLanguages(checks3) {
|
|
4411
4396
|
const languages = [];
|
|
4412
|
-
|
|
4413
|
-
if (checks3
|
|
4414
|
-
languages.push(
|
|
4415
|
-
|
|
4416
|
-
|
|
4397
|
+
for (const detector of LANGUAGE_DETECTORS) {
|
|
4398
|
+
if (detector.check(checks3)) {
|
|
4399
|
+
languages.push(detector.name);
|
|
4400
|
+
if (detector.includes) {
|
|
4401
|
+
languages.push(...detector.includes);
|
|
4402
|
+
}
|
|
4417
4403
|
}
|
|
4418
4404
|
}
|
|
4419
|
-
if (checks3.hasRequirements || checks3.hasPyproject) {
|
|
4420
|
-
languages.push("python");
|
|
4421
|
-
}
|
|
4422
|
-
if (checks3.hasPom || checks3.hasGradle) {
|
|
4423
|
-
languages.push("java");
|
|
4424
|
-
}
|
|
4425
|
-
if (checks3.hasGoMod) {
|
|
4426
|
-
languages.push("go");
|
|
4427
|
-
}
|
|
4428
|
-
if (checks3.hasCargo) {
|
|
4429
|
-
languages.push("rust");
|
|
4430
|
-
}
|
|
4431
|
-
if (checks3.hasComposer) {
|
|
4432
|
-
languages.push("php");
|
|
4433
|
-
}
|
|
4434
|
-
if (checks3.hasGemfile) {
|
|
4435
|
-
languages.push("ruby");
|
|
4436
|
-
}
|
|
4437
4405
|
return languages.length > 0 ? languages : ["generic"];
|
|
4438
4406
|
}
|
|
4439
4407
|
function detectSourceDirectories(directory) {
|
|
@@ -4470,11 +4438,25 @@ async function detectProjectType(directory) {
|
|
|
4470
4438
|
});
|
|
4471
4439
|
return result;
|
|
4472
4440
|
}
|
|
4473
|
-
var logger2;
|
|
4441
|
+
var logger2, LANGUAGE_DETECTORS;
|
|
4474
4442
|
var init_detection = __esm(() => {
|
|
4475
4443
|
init_logger();
|
|
4476
4444
|
init_patterns();
|
|
4477
4445
|
logger2 = new Logger("scanner-config");
|
|
4446
|
+
LANGUAGE_DETECTORS = [
|
|
4447
|
+
{
|
|
4448
|
+
name: "typescript",
|
|
4449
|
+
check: (c) => c.hasPackageJson && c.hasTsConfig,
|
|
4450
|
+
includes: ["javascript"]
|
|
4451
|
+
},
|
|
4452
|
+
{ name: "javascript", check: (c) => c.hasPackageJson && !c.hasTsConfig },
|
|
4453
|
+
{ name: "python", check: (c) => c.hasRequirements || c.hasPyproject },
|
|
4454
|
+
{ name: "java", check: (c) => c.hasPom || c.hasGradle },
|
|
4455
|
+
{ name: "go", check: (c) => c.hasGoMod },
|
|
4456
|
+
{ name: "rust", check: (c) => c.hasCargo },
|
|
4457
|
+
{ name: "php", check: (c) => c.hasComposer },
|
|
4458
|
+
{ name: "ruby", check: (c) => c.hasGemfile }
|
|
4459
|
+
];
|
|
4478
4460
|
});
|
|
4479
4461
|
|
|
4480
4462
|
// src/scanner/config/properties.ts
|
|
@@ -4573,6 +4555,21 @@ class BaseAPI {
|
|
|
4573
4555
|
this.client = client;
|
|
4574
4556
|
this.logger = logger4 ?? new Logger(serviceName);
|
|
4575
4557
|
}
|
|
4558
|
+
validateProjectKey(projectKey, methodName) {
|
|
4559
|
+
if (!projectKey) {
|
|
4560
|
+
this.logger.error(`${methodName}: projectKey is empty/undefined!`);
|
|
4561
|
+
return false;
|
|
4562
|
+
}
|
|
4563
|
+
return true;
|
|
4564
|
+
}
|
|
4565
|
+
async safeRequest(request, fallback, context) {
|
|
4566
|
+
try {
|
|
4567
|
+
return await request();
|
|
4568
|
+
} catch (error45) {
|
|
4569
|
+
this.logger.warn(`${context}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
4570
|
+
return fallback;
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4576
4573
|
}
|
|
4577
4574
|
var init_base_api = __esm(() => {
|
|
4578
4575
|
init_logger();
|
|
@@ -17553,9 +17550,39 @@ init_base_api();
|
|
|
17553
17550
|
init_client();
|
|
17554
17551
|
init_projects();
|
|
17555
17552
|
|
|
17553
|
+
// src/utils/paths.ts
|
|
17554
|
+
function extractFilePathFromComponentKey(componentKey) {
|
|
17555
|
+
const colonIndex = componentKey.indexOf(":");
|
|
17556
|
+
if (colonIndex >= 0) {
|
|
17557
|
+
return componentKey.substring(colonIndex + 1);
|
|
17558
|
+
}
|
|
17559
|
+
return componentKey;
|
|
17560
|
+
}
|
|
17561
|
+
function shortenPath(path, maxLength = 50) {
|
|
17562
|
+
if (path.length <= maxLength)
|
|
17563
|
+
return path;
|
|
17564
|
+
return "..." + path.slice(-(maxLength - 3));
|
|
17565
|
+
}
|
|
17566
|
+
|
|
17556
17567
|
// src/api/issues.ts
|
|
17557
17568
|
init_base_api();
|
|
17558
17569
|
|
|
17570
|
+
// src/constants.ts
|
|
17571
|
+
var API = {
|
|
17572
|
+
DEFAULT_PAGE_SIZE: 500,
|
|
17573
|
+
DEFAULT_LIMIT: 20,
|
|
17574
|
+
MAX_PAGES: 10,
|
|
17575
|
+
POLL_INTERVAL_MS: 2000,
|
|
17576
|
+
ANALYSIS_TIMEOUT_MS: 300000,
|
|
17577
|
+
SOURCE_CONTEXT_LINES: 5
|
|
17578
|
+
};
|
|
17579
|
+
var RATINGS = ["A", "B", "C", "D", "E"];
|
|
17580
|
+
function numericToRating(value) {
|
|
17581
|
+
const index = Math.min(Math.max(Math.round(value) - 1, 0), 4);
|
|
17582
|
+
return RATINGS[index];
|
|
17583
|
+
}
|
|
17584
|
+
|
|
17585
|
+
// src/api/issues.ts
|
|
17559
17586
|
class IssuesAPI extends BaseAPI {
|
|
17560
17587
|
constructor(client, logger4) {
|
|
17561
17588
|
super(client, "sonarqube-issues", logger4);
|
|
@@ -17570,7 +17597,7 @@ class IssuesAPI extends BaseAPI {
|
|
|
17570
17597
|
resolved: options.resolved === undefined ? undefined : String(options.resolved),
|
|
17571
17598
|
branch: options.onBranch,
|
|
17572
17599
|
inNewCodePeriod: options.inNewCode === undefined ? undefined : String(options.inNewCode),
|
|
17573
|
-
ps: options.pageSize ??
|
|
17600
|
+
ps: options.pageSize ?? API.DEFAULT_PAGE_SIZE,
|
|
17574
17601
|
p: options.page ?? 1
|
|
17575
17602
|
});
|
|
17576
17603
|
}
|
|
@@ -17585,7 +17612,7 @@ class IssuesAPI extends BaseAPI {
|
|
|
17585
17612
|
types: options.types,
|
|
17586
17613
|
resolved: false,
|
|
17587
17614
|
page,
|
|
17588
|
-
pageSize:
|
|
17615
|
+
pageSize: API.DEFAULT_PAGE_SIZE
|
|
17589
17616
|
});
|
|
17590
17617
|
allIssues.push(...response.issues);
|
|
17591
17618
|
const totalPages = Math.ceil(response.paging.total / response.paging.pageSize);
|
|
@@ -17621,11 +17648,7 @@ class IssuesAPI extends BaseAPI {
|
|
|
17621
17648
|
}
|
|
17622
17649
|
return issues.map((issue2) => {
|
|
17623
17650
|
const componentPath = componentPaths.get(issue2.component);
|
|
17624
|
-
|
|
17625
|
-
const colonIndex = filePath.indexOf(":");
|
|
17626
|
-
if (colonIndex !== -1) {
|
|
17627
|
-
filePath = filePath.substring(colonIndex + 1);
|
|
17628
|
-
}
|
|
17651
|
+
const filePath = extractFilePathFromComponentKey(componentPath ?? issue2.component);
|
|
17629
17652
|
return {
|
|
17630
17653
|
key: issue2.key,
|
|
17631
17654
|
severity: issue2.severity,
|
|
@@ -17685,7 +17708,7 @@ class IssuesAPI extends BaseAPI {
|
|
|
17685
17708
|
try {
|
|
17686
17709
|
const response = await this.client.get("/api/hotspots/search", {
|
|
17687
17710
|
projectKey,
|
|
17688
|
-
ps:
|
|
17711
|
+
ps: API.DEFAULT_PAGE_SIZE,
|
|
17689
17712
|
p: 1
|
|
17690
17713
|
});
|
|
17691
17714
|
this.logger.info(`Found ${response.hotspots.length} security hotspots for project ${projectKey}`);
|
|
@@ -18045,7 +18068,6 @@ class QualityGateAPI extends BaseAPI {
|
|
|
18045
18068
|
}
|
|
18046
18069
|
// src/api/rules.ts
|
|
18047
18070
|
init_base_api();
|
|
18048
|
-
|
|
18049
18071
|
class RulesAPI extends BaseAPI {
|
|
18050
18072
|
cache = new Map;
|
|
18051
18073
|
constructor(client, logger4) {
|
|
@@ -18107,7 +18129,7 @@ class RulesAPI extends BaseAPI {
|
|
|
18107
18129
|
try {
|
|
18108
18130
|
const response = await this.client.get("/api/rules/search", {
|
|
18109
18131
|
rule_key: uncached.join(","),
|
|
18110
|
-
ps:
|
|
18132
|
+
ps: API.DEFAULT_PAGE_SIZE
|
|
18111
18133
|
});
|
|
18112
18134
|
for (const rule of response.rules) {
|
|
18113
18135
|
const details = this.parseRuleResponse(rule);
|
|
@@ -18148,6 +18170,32 @@ class RulesAPI extends BaseAPI {
|
|
|
18148
18170
|
// src/api/sources.ts
|
|
18149
18171
|
init_base_api();
|
|
18150
18172
|
|
|
18173
|
+
// src/utils/group-by.ts
|
|
18174
|
+
function groupBy(items, keyFn) {
|
|
18175
|
+
const groups = {};
|
|
18176
|
+
for (const item of items) {
|
|
18177
|
+
const key = keyFn(item);
|
|
18178
|
+
if (!groups[key]) {
|
|
18179
|
+
groups[key] = [];
|
|
18180
|
+
}
|
|
18181
|
+
groups[key].push(item);
|
|
18182
|
+
}
|
|
18183
|
+
return groups;
|
|
18184
|
+
}
|
|
18185
|
+
|
|
18186
|
+
// src/api/sources.ts
|
|
18187
|
+
function createIssueWithContext(issue2, sourceContext) {
|
|
18188
|
+
return {
|
|
18189
|
+
key: issue2.key,
|
|
18190
|
+
rule: issue2.rule,
|
|
18191
|
+
severity: issue2.severity,
|
|
18192
|
+
message: issue2.message,
|
|
18193
|
+
component: issue2.component,
|
|
18194
|
+
line: issue2.line,
|
|
18195
|
+
sourceContext
|
|
18196
|
+
};
|
|
18197
|
+
}
|
|
18198
|
+
|
|
18151
18199
|
class SourcesAPI extends BaseAPI {
|
|
18152
18200
|
constructor(client, logger4) {
|
|
18153
18201
|
super(client, "sonarqube-sources", logger4);
|
|
@@ -18181,20 +18229,12 @@ class SourcesAPI extends BaseAPI {
|
|
|
18181
18229
|
};
|
|
18182
18230
|
}
|
|
18183
18231
|
groupIssuesByComponent(issues) {
|
|
18184
|
-
const grouped =
|
|
18185
|
-
|
|
18186
|
-
const existing = grouped.get(issue2.component) ?? [];
|
|
18187
|
-
existing.push(issue2);
|
|
18188
|
-
grouped.set(issue2.component, existing);
|
|
18189
|
-
}
|
|
18190
|
-
return grouped;
|
|
18232
|
+
const grouped = groupBy(issues, (issue2) => issue2.component);
|
|
18233
|
+
return new Map(Object.entries(grouped));
|
|
18191
18234
|
}
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
}
|
|
18196
|
-
const contextStart = Math.max(1, issue2.line - contextLines);
|
|
18197
|
-
const contextEnd = issue2.line + contextLines;
|
|
18235
|
+
buildSourceContext(component, issueLine, lineMap, contextLines) {
|
|
18236
|
+
const contextStart = Math.max(1, issueLine - contextLines);
|
|
18237
|
+
const contextEnd = issueLine + contextLines;
|
|
18198
18238
|
const contextSourceLines = [];
|
|
18199
18239
|
for (let l = contextStart;l <= contextEnd; l++) {
|
|
18200
18240
|
const sourceLine = lineMap.get(l);
|
|
@@ -18203,20 +18243,21 @@ class SourcesAPI extends BaseAPI {
|
|
|
18203
18243
|
}
|
|
18204
18244
|
}
|
|
18205
18245
|
return {
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
startLine: contextStart,
|
|
18211
|
-
endLine: contextEnd
|
|
18212
|
-
}
|
|
18246
|
+
component,
|
|
18247
|
+
lines: contextSourceLines,
|
|
18248
|
+
startLine: contextStart,
|
|
18249
|
+
endLine: contextEnd
|
|
18213
18250
|
};
|
|
18214
18251
|
}
|
|
18252
|
+
createIssueWithSourceContext(issue2, lineMap, contextLines) {
|
|
18253
|
+
if (issue2.line === undefined) {
|
|
18254
|
+
return createIssueWithContext(issue2);
|
|
18255
|
+
}
|
|
18256
|
+
const sourceContext = this.buildSourceContext(issue2.component, issue2.line, lineMap, contextLines);
|
|
18257
|
+
return createIssueWithContext(issue2, sourceContext);
|
|
18258
|
+
}
|
|
18215
18259
|
addIssuesWithoutContext(issues) {
|
|
18216
|
-
return issues.map((issue2) => (
|
|
18217
|
-
...issue2,
|
|
18218
|
-
sourceContext: undefined
|
|
18219
|
-
}));
|
|
18260
|
+
return issues.map((issue2) => createIssueWithContext(issue2));
|
|
18220
18261
|
}
|
|
18221
18262
|
async getIssuesWithContext(issues, contextLines = 3) {
|
|
18222
18263
|
const results = [];
|
|
@@ -18233,7 +18274,7 @@ class SourcesAPI extends BaseAPI {
|
|
|
18233
18274
|
const sourceLines = await this.getSourceLines(component, minLine, maxLine);
|
|
18234
18275
|
const lineMap = new Map(sourceLines.map((l) => [l.line, l]));
|
|
18235
18276
|
for (const issue2 of componentIssues) {
|
|
18236
|
-
results.push(this.
|
|
18277
|
+
results.push(this.createIssueWithSourceContext(issue2, lineMap, contextLines));
|
|
18237
18278
|
}
|
|
18238
18279
|
} catch {
|
|
18239
18280
|
results.push(...this.addIssuesWithoutContext(componentIssues));
|
|
@@ -18254,7 +18295,7 @@ class SourcesAPI extends BaseAPI {
|
|
|
18254
18295
|
return ["", "```", ...codeLines, "```"];
|
|
18255
18296
|
}
|
|
18256
18297
|
formatIssueWithContext(issue2) {
|
|
18257
|
-
const filePath =
|
|
18298
|
+
const filePath = extractFilePathFromComponentKey(issue2.component);
|
|
18258
18299
|
const lines = [
|
|
18259
18300
|
`### ${issue2.severity}: ${issue2.message}`,
|
|
18260
18301
|
this.formatFileLocation(filePath, issue2.line),
|
|
@@ -18339,12 +18380,12 @@ class DuplicationsAPI extends BaseAPI {
|
|
|
18339
18380
|
if (!originalFile || !duplicateFile)
|
|
18340
18381
|
continue;
|
|
18341
18382
|
result.push({
|
|
18342
|
-
originalFile:
|
|
18383
|
+
originalFile: extractFilePathFromComponentKey(originalFile.key),
|
|
18343
18384
|
originalLines: {
|
|
18344
18385
|
from: originalBlock.from,
|
|
18345
18386
|
to: originalBlock.from + originalBlock.size - 1
|
|
18346
18387
|
},
|
|
18347
|
-
duplicateFile:
|
|
18388
|
+
duplicateFile: extractFilePathFromComponentKey(duplicateFile.key),
|
|
18348
18389
|
duplicateLines: {
|
|
18349
18390
|
from: dupBlock.from,
|
|
18350
18391
|
to: dupBlock.from + dupBlock.size - 1
|
|
@@ -18385,9 +18426,6 @@ class DuplicationsAPI extends BaseAPI {
|
|
|
18385
18426
|
return lines.join(`
|
|
18386
18427
|
`);
|
|
18387
18428
|
}
|
|
18388
|
-
extractFilePath(componentKey) {
|
|
18389
|
-
return componentKey.includes(":") ? componentKey.split(":").slice(1).join(":") : componentKey;
|
|
18390
|
-
}
|
|
18391
18429
|
}
|
|
18392
18430
|
// src/api/ce.ts
|
|
18393
18431
|
init_base_api();
|
|
@@ -18433,7 +18471,7 @@ class ComputeEngineAPI extends BaseAPI {
|
|
|
18433
18471
|
onlyCurrents: options.onlyCurrents?.toString(),
|
|
18434
18472
|
maxExecutedAt: options.maxExecutedAt,
|
|
18435
18473
|
minSubmittedAt: options.minSubmittedAt,
|
|
18436
|
-
ps: options.pageSize ??
|
|
18474
|
+
ps: options.pageSize ?? API.DEFAULT_LIMIT
|
|
18437
18475
|
});
|
|
18438
18476
|
return response.tasks;
|
|
18439
18477
|
} catch (error45) {
|
|
@@ -18452,29 +18490,42 @@ class ComputeEngineAPI extends BaseAPI {
|
|
|
18452
18490
|
const { current, queue } = await this.getComponentTasks(componentKey);
|
|
18453
18491
|
return current !== undefined || queue.length > 0;
|
|
18454
18492
|
}
|
|
18455
|
-
|
|
18493
|
+
shouldContinuePolling(startTime, maxWaitMs) {
|
|
18494
|
+
return Date.now() - startTime < maxWaitMs;
|
|
18495
|
+
}
|
|
18496
|
+
isTaskComplete(status) {
|
|
18497
|
+
return ["SUCCESS", "FAILED", "CANCELED"].includes(status);
|
|
18498
|
+
}
|
|
18499
|
+
handleTaskNotFound(taskId, retries, maxRetries) {
|
|
18500
|
+
if (retries >= maxRetries) {
|
|
18501
|
+
this.logger.warn(`Failed to get task ${taskId}: Not found after ${maxRetries} retries`);
|
|
18502
|
+
return { shouldContinue: false, newRetries: retries };
|
|
18503
|
+
}
|
|
18504
|
+
return { shouldContinue: true, newRetries: retries + 1 };
|
|
18505
|
+
}
|
|
18506
|
+
async waitForTask(taskId, pollIntervalMs = API.POLL_INTERVAL_MS, maxWaitMs = API.ANALYSIS_TIMEOUT_MS) {
|
|
18456
18507
|
this.logger.info(`Waiting for task to complete: ${taskId}`);
|
|
18457
18508
|
const startTime = Date.now();
|
|
18458
18509
|
let taskNotFoundRetries = 0;
|
|
18459
18510
|
const maxTaskNotFoundRetries = 10;
|
|
18460
18511
|
debugLog(`waitForTask: starting for ${taskId}`);
|
|
18461
|
-
while (
|
|
18512
|
+
while (this.shouldContinuePolling(startTime, maxWaitMs)) {
|
|
18462
18513
|
const task = await this.getTask(taskId);
|
|
18463
18514
|
if (!task) {
|
|
18464
|
-
taskNotFoundRetries
|
|
18465
|
-
|
|
18466
|
-
if (
|
|
18467
|
-
this.logger.warn(`Task ${taskId} not found after ${maxTaskNotFoundRetries} retries`);
|
|
18515
|
+
const result = this.handleTaskNotFound(taskId, taskNotFoundRetries, maxTaskNotFoundRetries);
|
|
18516
|
+
taskNotFoundRetries = result.newRetries;
|
|
18517
|
+
if (!result.shouldContinue) {
|
|
18468
18518
|
debugLog(`waitForTask: giving up after ${maxTaskNotFoundRetries} retries`);
|
|
18469
18519
|
return;
|
|
18470
18520
|
}
|
|
18521
|
+
debugLog(`waitForTask: task not found, retry ${taskNotFoundRetries}/${maxTaskNotFoundRetries}`);
|
|
18471
18522
|
this.logger.debug(`Task ${taskId} not found yet, retrying (${taskNotFoundRetries}/${maxTaskNotFoundRetries})...`);
|
|
18472
18523
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
18473
18524
|
continue;
|
|
18474
18525
|
}
|
|
18475
18526
|
taskNotFoundRetries = 0;
|
|
18476
18527
|
debugLog(`waitForTask: task found, status=${task.status}`);
|
|
18477
|
-
if (
|
|
18528
|
+
if (this.isTaskComplete(task.status)) {
|
|
18478
18529
|
this.logger.info(`Task completed with status: ${task.status}`);
|
|
18479
18530
|
debugLog(`waitForTask: task completed with ${task.status}`);
|
|
18480
18531
|
return task;
|
|
@@ -18486,7 +18537,7 @@ class ComputeEngineAPI extends BaseAPI {
|
|
|
18486
18537
|
debugLog(`waitForTask: timeout after ${maxWaitMs}ms`);
|
|
18487
18538
|
return;
|
|
18488
18539
|
}
|
|
18489
|
-
async waitForAnalysis(componentKey, timeoutMs =
|
|
18540
|
+
async waitForAnalysis(componentKey, timeoutMs = API.ANALYSIS_TIMEOUT_MS, pollIntervalMs = API.POLL_INTERVAL_MS) {
|
|
18490
18541
|
this.logger.info(`Waiting for analysis to complete: ${componentKey}`);
|
|
18491
18542
|
const startTime = Date.now();
|
|
18492
18543
|
let lastTaskId;
|
|
@@ -18550,8 +18601,7 @@ class ProjectAnalysesAPI extends BaseAPI {
|
|
|
18550
18601
|
projectKey: options.projectKey,
|
|
18551
18602
|
projectKeyLength: options.projectKey?.length
|
|
18552
18603
|
});
|
|
18553
|
-
if (!options.projectKey) {
|
|
18554
|
-
this.logger.error(`getAnalyses: projectKey is empty/undefined!`);
|
|
18604
|
+
if (!this.validateProjectKey(options.projectKey, "getAnalyses")) {
|
|
18555
18605
|
return [];
|
|
18556
18606
|
}
|
|
18557
18607
|
try {
|
|
@@ -18614,18 +18664,14 @@ class QualityProfilesAPI extends BaseAPI {
|
|
|
18614
18664
|
}
|
|
18615
18665
|
async getProjectProfiles(projectKey) {
|
|
18616
18666
|
this.logger.info(`>>> getProjectProfiles called`, { projectKey, projectKeyLength: projectKey?.length });
|
|
18617
|
-
if (!projectKey) {
|
|
18618
|
-
this.logger.error(`getProjectProfiles: projectKey is empty/undefined!`);
|
|
18667
|
+
if (!this.validateProjectKey(projectKey, "getProjectProfiles")) {
|
|
18619
18668
|
return [];
|
|
18620
18669
|
}
|
|
18621
|
-
|
|
18670
|
+
return this.safeRequest(async () => {
|
|
18622
18671
|
const response = await this.client.get("/api/qualityprofiles/search", { project: projectKey });
|
|
18623
18672
|
this.logger.info(`<<< getProjectProfiles success`, { profileCount: response.profiles.length });
|
|
18624
18673
|
return response.profiles;
|
|
18625
|
-
}
|
|
18626
|
-
this.logger.error(`getProjectProfiles failed`, { error: String(error45), projectKey });
|
|
18627
|
-
return [];
|
|
18628
|
-
}
|
|
18674
|
+
}, [], `getProjectProfiles failed for ${projectKey}`);
|
|
18629
18675
|
}
|
|
18630
18676
|
async getAllProfiles(language) {
|
|
18631
18677
|
const languageInfo = language ? ` for ${language}` : "";
|
|
@@ -18640,12 +18686,9 @@ class QualityProfilesAPI extends BaseAPI {
|
|
|
18640
18686
|
}
|
|
18641
18687
|
async getInheritance(profileKey) {
|
|
18642
18688
|
this.logger.debug(`Getting inheritance for profile ${profileKey}`);
|
|
18643
|
-
|
|
18689
|
+
return this.safeRequest(async () => {
|
|
18644
18690
|
return await this.client.get("/api/qualityprofiles/inheritance", { qualityProfile: profileKey });
|
|
18645
|
-
}
|
|
18646
|
-
this.logger.warn(`Failed to get inheritance: ${error45}`);
|
|
18647
|
-
return;
|
|
18648
|
-
}
|
|
18691
|
+
}, undefined, `Failed to get inheritance for ${profileKey}`);
|
|
18649
18692
|
}
|
|
18650
18693
|
formatProfilesForAgent(profiles, projectKey) {
|
|
18651
18694
|
if (profiles.length === 0) {
|
|
@@ -18705,18 +18748,14 @@ class BranchesAPI extends BaseAPI {
|
|
|
18705
18748
|
}
|
|
18706
18749
|
async getBranches(projectKey) {
|
|
18707
18750
|
this.logger.info(`>>> getBranches called`, { projectKey, projectKeyLength: projectKey?.length });
|
|
18708
|
-
if (!projectKey) {
|
|
18709
|
-
this.logger.error(`getBranches: projectKey is empty/undefined!`);
|
|
18751
|
+
if (!this.validateProjectKey(projectKey, "getBranches")) {
|
|
18710
18752
|
return [];
|
|
18711
18753
|
}
|
|
18712
|
-
|
|
18754
|
+
return this.safeRequest(async () => {
|
|
18713
18755
|
const response = await this.client.get("/api/project_branches/list", { project: projectKey });
|
|
18714
18756
|
this.logger.info(`<<< getBranches success`, { branchCount: response.branches.length });
|
|
18715
18757
|
return response.branches;
|
|
18716
|
-
}
|
|
18717
|
-
this.logger.error(`getBranches failed`, { error: String(error45), projectKey });
|
|
18718
|
-
return [];
|
|
18719
|
-
}
|
|
18758
|
+
}, [], `getBranches failed for ${projectKey}`);
|
|
18720
18759
|
}
|
|
18721
18760
|
async getMainBranch(projectKey) {
|
|
18722
18761
|
const branches = await this.getBranches(projectKey);
|
|
@@ -18728,16 +18767,13 @@ class BranchesAPI extends BaseAPI {
|
|
|
18728
18767
|
}
|
|
18729
18768
|
async deleteBranch(projectKey, branchName) {
|
|
18730
18769
|
this.logger.debug(`Deleting branch ${branchName} from ${projectKey}`);
|
|
18731
|
-
|
|
18770
|
+
return this.safeRequest(async () => {
|
|
18732
18771
|
await this.client.post("/api/project_branches/delete", {
|
|
18733
18772
|
project: projectKey,
|
|
18734
18773
|
branch: branchName
|
|
18735
18774
|
});
|
|
18736
18775
|
return true;
|
|
18737
|
-
}
|
|
18738
|
-
this.logger.warn(`Failed to delete branch: ${error45}`);
|
|
18739
|
-
return false;
|
|
18740
|
-
}
|
|
18776
|
+
}, false, `Failed to delete branch ${branchName}`);
|
|
18741
18777
|
}
|
|
18742
18778
|
async renameMainBranch(projectKey, newName) {
|
|
18743
18779
|
this.logger.debug(`Renaming main branch to ${newName} in ${projectKey}`);
|
|
@@ -18819,6 +18855,36 @@ class BranchesAPI extends BaseAPI {
|
|
|
18819
18855
|
}
|
|
18820
18856
|
// src/api/metrics.ts
|
|
18821
18857
|
init_base_api();
|
|
18858
|
+
|
|
18859
|
+
// src/utils/metric-formatter.ts
|
|
18860
|
+
var MetricFormatter = {
|
|
18861
|
+
rating(value) {
|
|
18862
|
+
const num = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
18863
|
+
return numericToRating(num);
|
|
18864
|
+
},
|
|
18865
|
+
percentage(value, decimals = 1) {
|
|
18866
|
+
const num = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
18867
|
+
return `${num.toFixed(decimals)}%`;
|
|
18868
|
+
},
|
|
18869
|
+
duration(minutes) {
|
|
18870
|
+
if (minutes < 60)
|
|
18871
|
+
return `${minutes}min`;
|
|
18872
|
+
const hours = Math.floor(minutes / 60);
|
|
18873
|
+
const mins = minutes % 60;
|
|
18874
|
+
return `${hours}h ${mins}min`;
|
|
18875
|
+
},
|
|
18876
|
+
formatByType(metricKey, value) {
|
|
18877
|
+
if (metricKey.includes("rating")) {
|
|
18878
|
+
return this.rating(value);
|
|
18879
|
+
}
|
|
18880
|
+
if (metricKey.includes("coverage") || metricKey.includes("duplicat")) {
|
|
18881
|
+
return this.percentage(value);
|
|
18882
|
+
}
|
|
18883
|
+
return value;
|
|
18884
|
+
}
|
|
18885
|
+
};
|
|
18886
|
+
|
|
18887
|
+
// src/api/metrics.ts
|
|
18822
18888
|
var COMMON_METRICS = [
|
|
18823
18889
|
"ncloc",
|
|
18824
18890
|
"lines",
|
|
@@ -18854,28 +18920,22 @@ class MetricsAPI extends BaseAPI {
|
|
|
18854
18920
|
}
|
|
18855
18921
|
async getMetricDefinitions() {
|
|
18856
18922
|
this.logger.debug("Getting metric definitions");
|
|
18857
|
-
|
|
18858
|
-
const response = await this.client.get("/api/metrics/search", { ps:
|
|
18923
|
+
return this.safeRequest(async () => {
|
|
18924
|
+
const response = await this.client.get("/api/metrics/search", { ps: API.DEFAULT_PAGE_SIZE });
|
|
18859
18925
|
return response.metrics;
|
|
18860
|
-
}
|
|
18861
|
-
this.logger.warn(`Failed to get metrics: ${error45}`);
|
|
18862
|
-
return [];
|
|
18863
|
-
}
|
|
18926
|
+
}, [], "Failed to get metric definitions");
|
|
18864
18927
|
}
|
|
18865
18928
|
async getMeasures(options) {
|
|
18866
18929
|
const metrics = options.metricKeys ?? [...COMMON_METRICS];
|
|
18867
18930
|
this.logger.debug(`Getting measures for ${options.componentKey}`);
|
|
18868
|
-
|
|
18931
|
+
return this.safeRequest(async () => {
|
|
18869
18932
|
const response = await this.client.get("/api/measures/component", {
|
|
18870
18933
|
component: options.componentKey,
|
|
18871
18934
|
metricKeys: metrics.join(","),
|
|
18872
18935
|
branch: options.branch
|
|
18873
18936
|
});
|
|
18874
18937
|
return response.component.measures;
|
|
18875
|
-
}
|
|
18876
|
-
this.logger.warn(`Failed to get measures: ${error45}`);
|
|
18877
|
-
return [];
|
|
18878
|
-
}
|
|
18938
|
+
}, [], `Failed to get measures for ${options.componentKey}`);
|
|
18879
18939
|
}
|
|
18880
18940
|
async getMeasuresWithPeriod(options) {
|
|
18881
18941
|
const metrics = options.metricKeys ?? [...COMMON_METRICS];
|
|
@@ -18956,19 +19016,21 @@ class MetricsAPI extends BaseAPI {
|
|
|
18956
19016
|
}
|
|
18957
19017
|
formatMetricValue(metricKey, value) {
|
|
18958
19018
|
if (metricKey.includes("rating")) {
|
|
18959
|
-
const
|
|
18960
|
-
|
|
18961
|
-
|
|
19019
|
+
const numericValue = Number.parseInt(value, 10);
|
|
19020
|
+
if (numericValue >= 1 && numericValue <= 5) {
|
|
19021
|
+
return MetricFormatter.rating(numericValue);
|
|
19022
|
+
}
|
|
19023
|
+
return value;
|
|
18962
19024
|
}
|
|
18963
19025
|
if (metricKey.includes("coverage") || metricKey.includes("density") || metricKey.includes("ratio")) {
|
|
18964
|
-
return
|
|
19026
|
+
return MetricFormatter.percentage(value, 1);
|
|
18965
19027
|
}
|
|
18966
19028
|
if (metricKey === "sqale_index") {
|
|
18967
19029
|
const minutes = Number.parseInt(value, 10);
|
|
18968
19030
|
if (minutes < 60)
|
|
18969
19031
|
return `${minutes}min`;
|
|
18970
19032
|
if (minutes < 1440)
|
|
18971
|
-
return
|
|
19033
|
+
return MetricFormatter.duration(minutes);
|
|
18972
19034
|
return `${Math.floor(minutes / 1440)}d`;
|
|
18973
19035
|
}
|
|
18974
19036
|
return value;
|
|
@@ -19058,8 +19120,7 @@ class ComponentsAPI extends BaseAPI {
|
|
|
19058
19120
|
"|------|------|-------|--------|-------|"
|
|
19059
19121
|
];
|
|
19060
19122
|
for (const file2 of files.slice(0, 20)) {
|
|
19061
|
-
|
|
19062
|
-
lines.push(`| ${shortPath} | ${file2.bugs} | ${file2.vulnerabilities} | ${file2.codeSmells} | **${file2.total}** |`);
|
|
19123
|
+
lines.push(`| ${shortenPath(file2.path)} | ${file2.bugs} | ${file2.vulnerabilities} | ${file2.codeSmells} | **${file2.total}** |`);
|
|
19063
19124
|
}
|
|
19064
19125
|
if (files.length > 20) {
|
|
19065
19126
|
lines.push(`
|
|
@@ -19077,14 +19138,6 @@ class ComponentsAPI extends BaseAPI {
|
|
|
19077
19138
|
return lines.join(`
|
|
19078
19139
|
`);
|
|
19079
19140
|
}
|
|
19080
|
-
shortenPath(path) {
|
|
19081
|
-
if (path.length <= 50)
|
|
19082
|
-
return path;
|
|
19083
|
-
const parts = path.split("/");
|
|
19084
|
-
if (parts.length <= 2)
|
|
19085
|
-
return path;
|
|
19086
|
-
return `.../${parts.slice(-2).join("/")}`;
|
|
19087
|
-
}
|
|
19088
19141
|
}
|
|
19089
19142
|
|
|
19090
19143
|
// src/api/index.ts
|
|
@@ -19156,6 +19209,18 @@ function createSonarQubeAPI(config3, state, logger4) {
|
|
|
19156
19209
|
init_config3();
|
|
19157
19210
|
|
|
19158
19211
|
// src/utils/severity.ts
|
|
19212
|
+
var SEVERITIES = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
|
|
19213
|
+
var SEVERITY_LEVELS = {
|
|
19214
|
+
blocker: ["BLOCKER"],
|
|
19215
|
+
critical: ["BLOCKER", "CRITICAL"],
|
|
19216
|
+
major: ["BLOCKER", "CRITICAL", "MAJOR"],
|
|
19217
|
+
minor: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"],
|
|
19218
|
+
info: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"],
|
|
19219
|
+
all: ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"]
|
|
19220
|
+
};
|
|
19221
|
+
function getSeveritiesFromLevel(level) {
|
|
19222
|
+
return SEVERITY_LEVELS[level.toLowerCase()] ?? [...SEVERITIES];
|
|
19223
|
+
}
|
|
19159
19224
|
var SEVERITY_PRIORITY = {
|
|
19160
19225
|
BLOCKER: 5,
|
|
19161
19226
|
CRITICAL: 4,
|
|
@@ -19652,6 +19717,128 @@ function formatEmptyState(entity, projectKey, positiveMessage) {
|
|
|
19652
19717
|
return lines.join(`
|
|
19653
19718
|
`);
|
|
19654
19719
|
}
|
|
19720
|
+
// src/tools/formatters/suggestions.ts
|
|
19721
|
+
function formatFixSuggestions(issues) {
|
|
19722
|
+
const blockers = issues.filter((i) => i.severity === "BLOCKER");
|
|
19723
|
+
const critical = issues.filter((i) => i.severity === "CRITICAL");
|
|
19724
|
+
if (blockers.length === 0 && critical.length === 0) {
|
|
19725
|
+
return "";
|
|
19726
|
+
}
|
|
19727
|
+
let output = `
|
|
19728
|
+
|
|
19729
|
+
---
|
|
19730
|
+
|
|
19731
|
+
## Fix Suggestions
|
|
19732
|
+
|
|
19733
|
+
`;
|
|
19734
|
+
output += `Based on the issues found, here are recommended actions:
|
|
19735
|
+
|
|
19736
|
+
`;
|
|
19737
|
+
if (blockers.length > 0) {
|
|
19738
|
+
output += `### Immediate Fixes Required (Blockers)
|
|
19739
|
+
`;
|
|
19740
|
+
for (const issue2 of blockers.slice(0, 5)) {
|
|
19741
|
+
output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
|
|
19742
|
+
`;
|
|
19743
|
+
output += ` - Rule: \`${issue2.rule}\` - Review and fix this security/reliability issue
|
|
19744
|
+
|
|
19745
|
+
`;
|
|
19746
|
+
}
|
|
19747
|
+
}
|
|
19748
|
+
if (critical.length > 0) {
|
|
19749
|
+
output += `### High Priority Fixes (Critical)
|
|
19750
|
+
`;
|
|
19751
|
+
for (const issue2 of critical.slice(0, 5)) {
|
|
19752
|
+
output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
|
|
19753
|
+
`;
|
|
19754
|
+
output += ` - Rule: \`${issue2.rule}\`
|
|
19755
|
+
|
|
19756
|
+
`;
|
|
19757
|
+
}
|
|
19758
|
+
}
|
|
19759
|
+
return output;
|
|
19760
|
+
}
|
|
19761
|
+
// src/utils/error-messages.ts
|
|
19762
|
+
var ErrorMessages = {
|
|
19763
|
+
missingParameter(param, example) {
|
|
19764
|
+
let msg = `Missing required parameter: ${param}`;
|
|
19765
|
+
if (example) {
|
|
19766
|
+
msg += `
|
|
19767
|
+
|
|
19768
|
+
Example: ${example}`;
|
|
19769
|
+
}
|
|
19770
|
+
return msg;
|
|
19771
|
+
},
|
|
19772
|
+
notFound(entity, key, suggestions) {
|
|
19773
|
+
let msg = `${entity} not found: ${key}`;
|
|
19774
|
+
if (suggestions?.length) {
|
|
19775
|
+
const suggestionList = suggestions.map((s) => "- " + s).join(`
|
|
19776
|
+
`);
|
|
19777
|
+
msg += `
|
|
19778
|
+
|
|
19779
|
+
Did you mean:
|
|
19780
|
+
` + suggestionList;
|
|
19781
|
+
}
|
|
19782
|
+
return msg;
|
|
19783
|
+
},
|
|
19784
|
+
configurationMissing(message, requiredSteps) {
|
|
19785
|
+
let msg = message;
|
|
19786
|
+
if (requiredSteps?.length) {
|
|
19787
|
+
const stepList = requiredSteps.map((s, i) => i + 1 + ". " + s).join(`
|
|
19788
|
+
`);
|
|
19789
|
+
msg += `
|
|
19790
|
+
|
|
19791
|
+
Required steps:
|
|
19792
|
+
` + stepList;
|
|
19793
|
+
}
|
|
19794
|
+
return msg;
|
|
19795
|
+
},
|
|
19796
|
+
apiError(action, message, serverUrl) {
|
|
19797
|
+
let msg = `Action \`${action}\` failed: ${message}`;
|
|
19798
|
+
const hints = [
|
|
19799
|
+
serverUrl ? `SonarQube server is reachable at ${serverUrl}` : "SonarQube server is reachable",
|
|
19800
|
+
"Credentials are valid",
|
|
19801
|
+
'Run with action: "setup" to initialize'
|
|
19802
|
+
];
|
|
19803
|
+
const hintList = hints.map((h, i) => i + 1 + ". " + h).join(`
|
|
19804
|
+
`);
|
|
19805
|
+
msg += `
|
|
19806
|
+
|
|
19807
|
+
Please check:
|
|
19808
|
+
` + hintList;
|
|
19809
|
+
return msg;
|
|
19810
|
+
},
|
|
19811
|
+
connectionError(serverUrl) {
|
|
19812
|
+
return `Cannot connect to SonarQube server at ${serverUrl}`;
|
|
19813
|
+
},
|
|
19814
|
+
authenticationError(reason) {
|
|
19815
|
+
return `Authentication failed: ${reason}`;
|
|
19816
|
+
},
|
|
19817
|
+
ruleNotFound(ruleKey) {
|
|
19818
|
+
return `Rule \`${ruleKey}\` was not found.
|
|
19819
|
+
|
|
19820
|
+
Please check:
|
|
19821
|
+
1. The rule key is correct (e.g., "typescript:S1234")
|
|
19822
|
+
2. The rule is available on your SonarQube server
|
|
19823
|
+
3. The rule's language plugin is installed`;
|
|
19824
|
+
},
|
|
19825
|
+
missingRuleKey() {
|
|
19826
|
+
return `Missing \`ruleKey\` parameter. Please provide a rule key to explain.
|
|
19827
|
+
|
|
19828
|
+
Example usage:
|
|
19829
|
+
\`\`\`
|
|
19830
|
+
sonarqube({ action: "rule", ruleKey: "typescript:S1234" })
|
|
19831
|
+
\`\`\`
|
|
19832
|
+
|
|
19833
|
+
Common rule prefixes:
|
|
19834
|
+
- \`typescript:\` - TypeScript rules
|
|
19835
|
+
- \`javascript:\` - JavaScript rules
|
|
19836
|
+
- \`java:\` - Java rules
|
|
19837
|
+
- \`python:\` - Python rules
|
|
19838
|
+
- \`common-\` - Language-agnostic rules`;
|
|
19839
|
+
}
|
|
19840
|
+
};
|
|
19841
|
+
|
|
19655
19842
|
// src/tools/base-handler.ts
|
|
19656
19843
|
function createHandlerContext(config3, state, projectKey, directory) {
|
|
19657
19844
|
return {
|
|
@@ -19757,63 +19944,11 @@ async function handleAnalyze(ctx, args) {
|
|
|
19757
19944
|
}, directory);
|
|
19758
19945
|
let output = formatAnalysisResult(result);
|
|
19759
19946
|
if (args.fix && result.formattedIssues.length > 0) {
|
|
19760
|
-
output +=
|
|
19761
|
-
|
|
19762
|
-
---
|
|
19763
|
-
|
|
19764
|
-
## Fix Suggestions
|
|
19765
|
-
|
|
19766
|
-
`;
|
|
19767
|
-
output += `Based on the issues found, here are recommended actions:
|
|
19768
|
-
|
|
19769
|
-
`;
|
|
19770
|
-
const blockers = result.formattedIssues.filter((i) => i.severity === "BLOCKER");
|
|
19771
|
-
const critical = result.formattedIssues.filter((i) => i.severity === "CRITICAL");
|
|
19772
|
-
if (blockers.length > 0) {
|
|
19773
|
-
output += `### Immediate Fixes Required (Blockers)
|
|
19774
|
-
`;
|
|
19775
|
-
for (const issue2 of blockers.slice(0, 5)) {
|
|
19776
|
-
output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
|
|
19777
|
-
`;
|
|
19778
|
-
output += ` - Rule: \`${issue2.rule}\` - Review and fix this security/reliability issue
|
|
19779
|
-
|
|
19780
|
-
`;
|
|
19781
|
-
}
|
|
19782
|
-
}
|
|
19783
|
-
if (critical.length > 0) {
|
|
19784
|
-
output += `### High Priority Fixes (Critical)
|
|
19785
|
-
`;
|
|
19786
|
-
for (const issue2 of critical.slice(0, 5)) {
|
|
19787
|
-
output += `1. **${issue2.file}:${issue2.line ?? "?"}** - ${issue2.message}
|
|
19788
|
-
`;
|
|
19789
|
-
output += ` - Rule: \`${issue2.rule}\`
|
|
19790
|
-
|
|
19791
|
-
`;
|
|
19792
|
-
}
|
|
19793
|
-
}
|
|
19947
|
+
output += formatFixSuggestions(result.formattedIssues);
|
|
19794
19948
|
}
|
|
19795
19949
|
return output;
|
|
19796
19950
|
}
|
|
19797
19951
|
// src/tools/handlers/issues.ts
|
|
19798
|
-
init_logger();
|
|
19799
|
-
var logger9 = new Logger("sonarqube-handler-issues");
|
|
19800
|
-
function getSeveritiesFromLevel(level) {
|
|
19801
|
-
switch (level.toLowerCase()) {
|
|
19802
|
-
case "blocker":
|
|
19803
|
-
return ["BLOCKER"];
|
|
19804
|
-
case "critical":
|
|
19805
|
-
return ["BLOCKER", "CRITICAL"];
|
|
19806
|
-
case "major":
|
|
19807
|
-
return ["BLOCKER", "CRITICAL", "MAJOR"];
|
|
19808
|
-
case "minor":
|
|
19809
|
-
return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"];
|
|
19810
|
-
case "info":
|
|
19811
|
-
case "all":
|
|
19812
|
-
return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
|
|
19813
|
-
default:
|
|
19814
|
-
return ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
|
|
19815
|
-
}
|
|
19816
|
-
}
|
|
19817
19952
|
async function handleIssues(ctx, args) {
|
|
19818
19953
|
const { api: api2, projectKey } = ctx;
|
|
19819
19954
|
const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
|
|
@@ -19836,7 +19971,7 @@ async function handleIssues(ctx, args) {
|
|
|
19836
19971
|
message: i.message,
|
|
19837
19972
|
component: `${projectKey}:${i.file}`,
|
|
19838
19973
|
line: i.line
|
|
19839
|
-
})),
|
|
19974
|
+
})), API.SOURCE_CONTEXT_LINES);
|
|
19840
19975
|
output += `
|
|
19841
19976
|
|
|
19842
19977
|
---
|
|
@@ -19853,9 +19988,7 @@ async function handleIssues(ctx, args) {
|
|
|
19853
19988
|
|
|
19854
19989
|
`;
|
|
19855
19990
|
}
|
|
19856
|
-
} catch {
|
|
19857
|
-
logger9.debug("Could not fetch source context for issues");
|
|
19858
|
-
}
|
|
19991
|
+
} catch {}
|
|
19859
19992
|
}
|
|
19860
19993
|
return output;
|
|
19861
19994
|
}
|
|
@@ -19867,7 +20000,7 @@ async function handleNewIssues(ctx, args) {
|
|
|
19867
20000
|
severities,
|
|
19868
20001
|
inNewCode: true,
|
|
19869
20002
|
resolved: false,
|
|
19870
|
-
pageSize:
|
|
20003
|
+
pageSize: API.DEFAULT_PAGE_SIZE
|
|
19871
20004
|
});
|
|
19872
20005
|
const issues = api2.issues.formatIssues(response.issues, response.components);
|
|
19873
20006
|
if (issues.length === 0) {
|
|
@@ -19889,8 +20022,7 @@ These issues were introduced in your recent changes and should be fixed before m
|
|
|
19889
20022
|
bySeverity[issue2.severity] = [];
|
|
19890
20023
|
bySeverity[issue2.severity].push(issue2);
|
|
19891
20024
|
}
|
|
19892
|
-
const
|
|
19893
|
-
for (const sev of severityOrder) {
|
|
20025
|
+
for (const sev of SEVERITIES) {
|
|
19894
20026
|
const sevIssues = bySeverity[sev];
|
|
19895
20027
|
if (!sevIssues || sevIssues.length === 0)
|
|
19896
20028
|
continue;
|
|
@@ -20066,31 +20198,14 @@ async function handleRule(ctx, ruleKey) {
|
|
|
20066
20198
|
if (!ruleKey) {
|
|
20067
20199
|
return `## SonarQube Error
|
|
20068
20200
|
|
|
20069
|
-
|
|
20070
|
-
|
|
20071
|
-
Example usage:
|
|
20072
|
-
\`\`\`
|
|
20073
|
-
sonarqube({ action: "rule", ruleKey: "typescript:S1234" })
|
|
20074
|
-
\`\`\`
|
|
20075
|
-
|
|
20076
|
-
Common rule prefixes:
|
|
20077
|
-
- \`typescript:\` - TypeScript rules
|
|
20078
|
-
- \`javascript:\` - JavaScript rules
|
|
20079
|
-
- \`java:\` - Java rules
|
|
20080
|
-
- \`python:\` - Python rules
|
|
20081
|
-
- \`common-\` - Language-agnostic rules`;
|
|
20201
|
+
${ErrorMessages.missingRuleKey()}`;
|
|
20082
20202
|
}
|
|
20083
20203
|
const { api: api2 } = ctx;
|
|
20084
20204
|
const rule = await api2.rules.getRule(ruleKey);
|
|
20085
20205
|
if (!rule) {
|
|
20086
20206
|
return `## SonarQube Rule Not Found
|
|
20087
20207
|
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
Please check:
|
|
20091
|
-
1. The rule key is correct (e.g., "typescript:S1234")
|
|
20092
|
-
2. The rule is available on your SonarQube server
|
|
20093
|
-
3. The rule's language plugin is installed`;
|
|
20208
|
+
${ErrorMessages.ruleNotFound(ruleKey)}`;
|
|
20094
20209
|
}
|
|
20095
20210
|
return `## SonarQube Rule: ${rule.name}
|
|
20096
20211
|
|
|
@@ -20186,7 +20301,7 @@ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
|
|
|
20186
20301
|
return output;
|
|
20187
20302
|
}
|
|
20188
20303
|
// src/tools/sonarqube.ts
|
|
20189
|
-
var
|
|
20304
|
+
var logger9 = new Logger("sonarqube-tool");
|
|
20190
20305
|
var SonarQubeToolArgsSchema = exports_external2.object({
|
|
20191
20306
|
action: exports_external2.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform: analyze (run scanner), issues (all issues), newissues (only new code issues), status (quality gate), init/setup (initialize), validate (enterprise check), hotspots (security review), duplications (code duplicates), rule (explain rule), history (past analyses), profile (quality profile), branches (branch status), metrics (detailed metrics), worstfiles (files with most issues)"),
|
|
20192
20307
|
scope: exports_external2.enum(["all", "new", "changed"]).optional().default("all").describe("Scope of analysis: all files, only new code, or changed files"),
|
|
@@ -20201,12 +20316,11 @@ async function executeSonarQubeTool(args, context) {
|
|
|
20201
20316
|
const directory = context.directory ?? process.cwd();
|
|
20202
20317
|
const config3 = loadConfig(context.config);
|
|
20203
20318
|
if (!config3) {
|
|
20204
|
-
return formatError2(
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
3. Plugin configuration in opencode.json
|
|
20319
|
+
return formatError2(ErrorMessages.configurationMissing("SonarQube configuration not found.", [
|
|
20320
|
+
"Set environment variables: SONAR_HOST_URL and SONAR_TOKEN",
|
|
20321
|
+
"Or create a .sonarqube/config.json file in your project",
|
|
20322
|
+
"Or add plugin configuration in opencode.json"
|
|
20323
|
+
]) + `
|
|
20210
20324
|
|
|
20211
20325
|
Required environment variables:
|
|
20212
20326
|
- SONAR_HOST_URL (e.g., https://sonarqube.company.com)
|
|
@@ -20216,13 +20330,13 @@ Optional:
|
|
|
20216
20330
|
- SONAR_USER
|
|
20217
20331
|
- SONAR_PASSWORD`);
|
|
20218
20332
|
}
|
|
20219
|
-
|
|
20333
|
+
logger9.info(`Executing SonarQube tool: ${args.action}`, { directory });
|
|
20220
20334
|
try {
|
|
20221
20335
|
if (args.action === "init" || args.action === "setup") {
|
|
20222
20336
|
return await handleSetup(config3, directory, args.force);
|
|
20223
20337
|
}
|
|
20224
20338
|
if (await needsBootstrap(directory)) {
|
|
20225
|
-
|
|
20339
|
+
logger9.info("First run detected, running bootstrap");
|
|
20226
20340
|
const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
|
|
20227
20341
|
const setupResult = await bootstrap2({ config: config3, directory });
|
|
20228
20342
|
if (!setupResult.success) {
|
|
@@ -20230,7 +20344,7 @@ Optional:
|
|
|
20230
20344
|
|
|
20231
20345
|
${setupResult.message}`);
|
|
20232
20346
|
}
|
|
20233
|
-
|
|
20347
|
+
logger9.info("Bootstrap completed", { projectKey: setupResult.projectKey });
|
|
20234
20348
|
}
|
|
20235
20349
|
const state = await getProjectState(directory);
|
|
20236
20350
|
if (!state) {
|
|
@@ -20270,13 +20384,8 @@ ${setupResult.message}`);
|
|
|
20270
20384
|
}
|
|
20271
20385
|
} catch (error45) {
|
|
20272
20386
|
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
20273
|
-
|
|
20274
|
-
return formatError2(
|
|
20275
|
-
|
|
20276
|
-
Please check:
|
|
20277
|
-
1. SonarQube server is reachable at ${config3.url}
|
|
20278
|
-
2. Credentials are valid
|
|
20279
|
-
3. Run with action: "setup" to initialize`);
|
|
20387
|
+
logger9.error(`Tool execution failed: ${errorMessage}`);
|
|
20388
|
+
return formatError2(ErrorMessages.apiError(args.action, errorMessage, config3.url));
|
|
20280
20389
|
}
|
|
20281
20390
|
}
|
|
20282
20391
|
|