opencode-sonarqube 1.3.0 → 1.4.1
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 +359 -248
- 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 {
|
|
@@ -19664,6 +19851,7 @@ function createHandlerContext(config3, state, projectKey, directory) {
|
|
|
19664
19851
|
}
|
|
19665
19852
|
// src/tools/handlers/setup.ts
|
|
19666
19853
|
init_bootstrap();
|
|
19854
|
+
init_detection();
|
|
19667
19855
|
init_logger();
|
|
19668
19856
|
import { mkdir } from "node:fs/promises";
|
|
19669
19857
|
var logger8 = new Logger("sonarqube-handler-setup");
|
|
@@ -19679,10 +19867,11 @@ ${result.message}`;
|
|
|
19679
19867
|
const configExists = await Bun.file(configPath).exists();
|
|
19680
19868
|
if (!configExists) {
|
|
19681
19869
|
await mkdir(sonarqubeDir, { recursive: true });
|
|
19870
|
+
const detectedSources = detectSourceDirectories(directory);
|
|
19682
19871
|
const defaultConfig = {
|
|
19683
19872
|
level: config3.level || "enterprise",
|
|
19684
19873
|
autoAnalyze: true,
|
|
19685
|
-
sources: config3.sources ||
|
|
19874
|
+
sources: config3.sources || detectedSources,
|
|
19686
19875
|
newCodeDefinition: "previous_version"
|
|
19687
19876
|
};
|
|
19688
19877
|
await Bun.write(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
@@ -19757,63 +19946,11 @@ async function handleAnalyze(ctx, args) {
|
|
|
19757
19946
|
}, directory);
|
|
19758
19947
|
let output = formatAnalysisResult(result);
|
|
19759
19948
|
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
|
-
}
|
|
19949
|
+
output += formatFixSuggestions(result.formattedIssues);
|
|
19794
19950
|
}
|
|
19795
19951
|
return output;
|
|
19796
19952
|
}
|
|
19797
19953
|
// 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
19954
|
async function handleIssues(ctx, args) {
|
|
19818
19955
|
const { api: api2, projectKey } = ctx;
|
|
19819
19956
|
const severities = args.severity === "all" ? undefined : getSeveritiesFromLevel(args.severity ?? "all");
|
|
@@ -19836,7 +19973,7 @@ async function handleIssues(ctx, args) {
|
|
|
19836
19973
|
message: i.message,
|
|
19837
19974
|
component: `${projectKey}:${i.file}`,
|
|
19838
19975
|
line: i.line
|
|
19839
|
-
})),
|
|
19976
|
+
})), API.SOURCE_CONTEXT_LINES);
|
|
19840
19977
|
output += `
|
|
19841
19978
|
|
|
19842
19979
|
---
|
|
@@ -19853,9 +19990,7 @@ async function handleIssues(ctx, args) {
|
|
|
19853
19990
|
|
|
19854
19991
|
`;
|
|
19855
19992
|
}
|
|
19856
|
-
} catch {
|
|
19857
|
-
logger9.debug("Could not fetch source context for issues");
|
|
19858
|
-
}
|
|
19993
|
+
} catch {}
|
|
19859
19994
|
}
|
|
19860
19995
|
return output;
|
|
19861
19996
|
}
|
|
@@ -19867,7 +20002,7 @@ async function handleNewIssues(ctx, args) {
|
|
|
19867
20002
|
severities,
|
|
19868
20003
|
inNewCode: true,
|
|
19869
20004
|
resolved: false,
|
|
19870
|
-
pageSize:
|
|
20005
|
+
pageSize: API.DEFAULT_PAGE_SIZE
|
|
19871
20006
|
});
|
|
19872
20007
|
const issues = api2.issues.formatIssues(response.issues, response.components);
|
|
19873
20008
|
if (issues.length === 0) {
|
|
@@ -19889,8 +20024,7 @@ These issues were introduced in your recent changes and should be fixed before m
|
|
|
19889
20024
|
bySeverity[issue2.severity] = [];
|
|
19890
20025
|
bySeverity[issue2.severity].push(issue2);
|
|
19891
20026
|
}
|
|
19892
|
-
const
|
|
19893
|
-
for (const sev of severityOrder) {
|
|
20027
|
+
for (const sev of SEVERITIES) {
|
|
19894
20028
|
const sevIssues = bySeverity[sev];
|
|
19895
20029
|
if (!sevIssues || sevIssues.length === 0)
|
|
19896
20030
|
continue;
|
|
@@ -20066,31 +20200,14 @@ async function handleRule(ctx, ruleKey) {
|
|
|
20066
20200
|
if (!ruleKey) {
|
|
20067
20201
|
return `## SonarQube Error
|
|
20068
20202
|
|
|
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`;
|
|
20203
|
+
${ErrorMessages.missingRuleKey()}`;
|
|
20082
20204
|
}
|
|
20083
20205
|
const { api: api2 } = ctx;
|
|
20084
20206
|
const rule = await api2.rules.getRule(ruleKey);
|
|
20085
20207
|
if (!rule) {
|
|
20086
20208
|
return `## SonarQube Rule Not Found
|
|
20087
20209
|
|
|
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`;
|
|
20210
|
+
${ErrorMessages.ruleNotFound(ruleKey)}`;
|
|
20094
20211
|
}
|
|
20095
20212
|
return `## SonarQube Rule: ${rule.name}
|
|
20096
20213
|
|
|
@@ -20186,7 +20303,7 @@ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
|
|
|
20186
20303
|
return output;
|
|
20187
20304
|
}
|
|
20188
20305
|
// src/tools/sonarqube.ts
|
|
20189
|
-
var
|
|
20306
|
+
var logger9 = new Logger("sonarqube-tool");
|
|
20190
20307
|
var SonarQubeToolArgsSchema = exports_external2.object({
|
|
20191
20308
|
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
20309
|
scope: exports_external2.enum(["all", "new", "changed"]).optional().default("all").describe("Scope of analysis: all files, only new code, or changed files"),
|
|
@@ -20201,12 +20318,11 @@ async function executeSonarQubeTool(args, context) {
|
|
|
20201
20318
|
const directory = context.directory ?? process.cwd();
|
|
20202
20319
|
const config3 = loadConfig(context.config);
|
|
20203
20320
|
if (!config3) {
|
|
20204
|
-
return formatError2(
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
3. Plugin configuration in opencode.json
|
|
20321
|
+
return formatError2(ErrorMessages.configurationMissing("SonarQube configuration not found.", [
|
|
20322
|
+
"Set environment variables: SONAR_HOST_URL and SONAR_TOKEN",
|
|
20323
|
+
"Or create a .sonarqube/config.json file in your project",
|
|
20324
|
+
"Or add plugin configuration in opencode.json"
|
|
20325
|
+
]) + `
|
|
20210
20326
|
|
|
20211
20327
|
Required environment variables:
|
|
20212
20328
|
- SONAR_HOST_URL (e.g., https://sonarqube.company.com)
|
|
@@ -20216,13 +20332,13 @@ Optional:
|
|
|
20216
20332
|
- SONAR_USER
|
|
20217
20333
|
- SONAR_PASSWORD`);
|
|
20218
20334
|
}
|
|
20219
|
-
|
|
20335
|
+
logger9.info(`Executing SonarQube tool: ${args.action}`, { directory });
|
|
20220
20336
|
try {
|
|
20221
20337
|
if (args.action === "init" || args.action === "setup") {
|
|
20222
20338
|
return await handleSetup(config3, directory, args.force);
|
|
20223
20339
|
}
|
|
20224
20340
|
if (await needsBootstrap(directory)) {
|
|
20225
|
-
|
|
20341
|
+
logger9.info("First run detected, running bootstrap");
|
|
20226
20342
|
const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
|
|
20227
20343
|
const setupResult = await bootstrap2({ config: config3, directory });
|
|
20228
20344
|
if (!setupResult.success) {
|
|
@@ -20230,7 +20346,7 @@ Optional:
|
|
|
20230
20346
|
|
|
20231
20347
|
${setupResult.message}`);
|
|
20232
20348
|
}
|
|
20233
|
-
|
|
20349
|
+
logger9.info("Bootstrap completed", { projectKey: setupResult.projectKey });
|
|
20234
20350
|
}
|
|
20235
20351
|
const state = await getProjectState(directory);
|
|
20236
20352
|
if (!state) {
|
|
@@ -20270,13 +20386,8 @@ ${setupResult.message}`);
|
|
|
20270
20386
|
}
|
|
20271
20387
|
} catch (error45) {
|
|
20272
20388
|
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`);
|
|
20389
|
+
logger9.error(`Tool execution failed: ${errorMessage}`);
|
|
20390
|
+
return formatError2(ErrorMessages.apiError(args.action, errorMessage, config3.url));
|
|
20280
20391
|
}
|
|
20281
20392
|
}
|
|
20282
20393
|
|