opencode-sonarqube 2.1.2 → 2.1.4
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 +114 -38
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -20378,9 +20378,50 @@ Please fix the failed checks before proceeding.`;
|
|
|
20378
20378
|
return output;
|
|
20379
20379
|
}
|
|
20380
20380
|
// src/tools/handlers/security.ts
|
|
20381
|
+
var HOTSPOT_AUTH_ERROR = `**Hotspot review requires admin credentials.**
|
|
20382
|
+
|
|
20383
|
+
Project tokens don't have permission to review security hotspots.
|
|
20384
|
+
The plugin needs valid admin credentials configured in one of these locations:
|
|
20385
|
+
|
|
20386
|
+
**Option 1: Environment variables**
|
|
20387
|
+
\`\`\`
|
|
20388
|
+
export SONAR_HOST_URL=https://your-sonarqube-server.com
|
|
20389
|
+
export SONAR_USER=admin
|
|
20390
|
+
export SONAR_PASSWORD=your-password
|
|
20391
|
+
\`\`\`
|
|
20392
|
+
|
|
20393
|
+
**Option 2: opencode.json**
|
|
20394
|
+
\`\`\`json
|
|
20395
|
+
{
|
|
20396
|
+
"plugins": {
|
|
20397
|
+
"opencode-sonarqube": {
|
|
20398
|
+
"url": "https://your-sonarqube-server.com",
|
|
20399
|
+
"user": "admin",
|
|
20400
|
+
"password": "your-password"
|
|
20401
|
+
}
|
|
20402
|
+
}
|
|
20403
|
+
}
|
|
20404
|
+
\`\`\`
|
|
20405
|
+
|
|
20406
|
+
**Option 3: .sonarqube/config.json** (in your project root)
|
|
20407
|
+
\`\`\`json
|
|
20408
|
+
{
|
|
20409
|
+
"url": "https://your-sonarqube-server.com",
|
|
20410
|
+
"user": "admin",
|
|
20411
|
+
"password": "your-password"
|
|
20412
|
+
}
|
|
20413
|
+
\`\`\`
|
|
20414
|
+
|
|
20415
|
+
The user needs the **"Administer Security Hotspot"** permission in SonarQube.`;
|
|
20381
20416
|
function createAdminAPI(ctx) {
|
|
20382
20417
|
return createSonarQubeAPIWithCredentials(ctx.config.url, ctx.config.user, ctx.config.password);
|
|
20383
20418
|
}
|
|
20419
|
+
function isAuthError(error45) {
|
|
20420
|
+
if (!(error45 instanceof Error))
|
|
20421
|
+
return false;
|
|
20422
|
+
const msg = error45.message.toLowerCase();
|
|
20423
|
+
return msg.includes("insufficient permissions") || msg.includes("invalid credentials") || msg.includes("401") || msg.includes("403");
|
|
20424
|
+
}
|
|
20384
20425
|
async function handleHotspots(ctx) {
|
|
20385
20426
|
const { api: api2, projectKey } = ctx;
|
|
20386
20427
|
const hotspots = await api2.issues.getSecurityHotspots(projectKey);
|
|
@@ -20448,57 +20489,92 @@ sonarqube({ action: "reviewhotspot", resolution: "SAFE", comment: "Bulk review:
|
|
|
20448
20489
|
}
|
|
20449
20490
|
return output;
|
|
20450
20491
|
}
|
|
20451
|
-
async function
|
|
20452
|
-
|
|
20453
|
-
|
|
20454
|
-
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
|
|
20458
|
-
|
|
20459
|
-
if (!hotspotKey) {
|
|
20460
|
-
const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
20461
|
-
if (toReview.length === 0) {
|
|
20462
|
-
return formatSuccess("Review Hotspots", "No pending hotspots to review. All hotspots have already been reviewed.");
|
|
20492
|
+
async function getAuthenticatedAdminAPI(ctx) {
|
|
20493
|
+
try {
|
|
20494
|
+
const adminApi = createAdminAPI(ctx);
|
|
20495
|
+
await adminApi.client.validateAuth();
|
|
20496
|
+
return adminApi;
|
|
20497
|
+
} catch (error45) {
|
|
20498
|
+
if (isAuthError(error45)) {
|
|
20499
|
+
return HOTSPOT_AUTH_ERROR;
|
|
20463
20500
|
}
|
|
20464
|
-
const
|
|
20465
|
-
|
|
20501
|
+
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
20502
|
+
return `**Error connecting to SonarQube:** ${msg}
|
|
20466
20503
|
|
|
20467
|
-
|
|
20468
|
-
|
|
20469
|
-
|
|
20470
|
-
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20504
|
+
${HOTSPOT_AUTH_ERROR}`;
|
|
20505
|
+
}
|
|
20506
|
+
}
|
|
20507
|
+
async function bulkReviewAllHotspots(api2, adminApi, projectKey, res, comment) {
|
|
20508
|
+
const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
20509
|
+
if (toReview.length === 0) {
|
|
20510
|
+
return formatSuccess("Review Hotspots", "No pending hotspots to review. All hotspots have already been reviewed.");
|
|
20511
|
+
}
|
|
20512
|
+
const result = await adminApi.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20513
|
+
if (result.failed > 0 && result.success === 0 && result.errors.some((e) => e.includes("Insufficient permissions"))) {
|
|
20514
|
+
return HOTSPOT_AUTH_ERROR;
|
|
20515
|
+
}
|
|
20516
|
+
return formatBulkReviewResult(projectKey, res, result);
|
|
20517
|
+
}
|
|
20518
|
+
function formatBulkReviewResult(projectKey, res, result) {
|
|
20519
|
+
const lines = [
|
|
20520
|
+
"## Hotspot Bulk Review Complete",
|
|
20521
|
+
"",
|
|
20522
|
+
`**Project:** \`${projectKey}\``,
|
|
20523
|
+
`**Resolution:** ${res}`,
|
|
20524
|
+
`**Reviewed:** ${result.success} hotspots`,
|
|
20525
|
+
`**Failed:** ${result.failed} hotspots`
|
|
20526
|
+
];
|
|
20527
|
+
if (result.errors.length > 0) {
|
|
20528
|
+
lines.push("", "### Errors");
|
|
20529
|
+
for (const err of result.errors.slice(0, 10)) {
|
|
20530
|
+
lines.push(`- ${err}`);
|
|
20480
20531
|
}
|
|
20481
|
-
output += `
|
|
20482
|
-
> Run \`sonarqube({ action: "status" })\` to check the updated Quality Gate.`;
|
|
20483
|
-
return output;
|
|
20484
20532
|
}
|
|
20533
|
+
lines.push("", '> Run `sonarqube({ action: "status" })` to check the updated Quality Gate.');
|
|
20534
|
+
return lines.join(`
|
|
20535
|
+
`);
|
|
20536
|
+
}
|
|
20537
|
+
async function reviewSingleHotspot(adminApi, hotspotKey, res, comment) {
|
|
20485
20538
|
try {
|
|
20486
20539
|
await adminApi.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20487
|
-
|
|
20488
|
-
|
|
20489
|
-
|
|
20490
|
-
|
|
20491
|
-
**
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20540
|
+
const lines = [
|
|
20541
|
+
"## Hotspot Reviewed",
|
|
20542
|
+
"",
|
|
20543
|
+
`**Hotspot:** \`${hotspotKey}\``,
|
|
20544
|
+
"**Status:** REVIEWED",
|
|
20545
|
+
`**Resolution:** ${res}`
|
|
20546
|
+
];
|
|
20547
|
+
if (comment) {
|
|
20548
|
+
lines.push(`**Comment:** ${comment}`);
|
|
20549
|
+
}
|
|
20550
|
+
lines.push("", '> Run `sonarqube({ action: "hotspots" })` to see remaining hotspots.');
|
|
20551
|
+
return lines.join(`
|
|
20552
|
+
`);
|
|
20495
20553
|
} catch (error45) {
|
|
20554
|
+
if (isAuthError(error45)) {
|
|
20555
|
+
return HOTSPOT_AUTH_ERROR;
|
|
20556
|
+
}
|
|
20496
20557
|
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
20497
20558
|
return `**Error reviewing hotspot:** ${msg}
|
|
20498
20559
|
|
|
20499
20560
|
Make sure the hotspot key is correct. Use \`sonarqube({ action: "hotspots" })\` to list hotspot keys.`;
|
|
20500
20561
|
}
|
|
20501
20562
|
}
|
|
20563
|
+
async function handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
|
|
20564
|
+
const validResolutions = ["SAFE", "FIXED", "ACKNOWLEDGED"];
|
|
20565
|
+
const res = resolution?.toUpperCase() ?? "SAFE";
|
|
20566
|
+
if (!validResolutions.includes(res)) {
|
|
20567
|
+
return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
|
|
20568
|
+
}
|
|
20569
|
+
const adminApiOrError = await getAuthenticatedAdminAPI(ctx);
|
|
20570
|
+
if (typeof adminApiOrError === "string") {
|
|
20571
|
+
return adminApiOrError;
|
|
20572
|
+
}
|
|
20573
|
+
if (!hotspotKey) {
|
|
20574
|
+
return bulkReviewAllHotspots(ctx.api, adminApiOrError, ctx.projectKey, res, comment);
|
|
20575
|
+
}
|
|
20576
|
+
return reviewSingleHotspot(adminApiOrError, hotspotKey, res, comment);
|
|
20577
|
+
}
|
|
20502
20578
|
// src/tools/handlers/duplications.ts
|
|
20503
20579
|
async function handleDuplications(ctx) {
|
|
20504
20580
|
const { api: api2, projectKey } = ctx;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-sonarqube",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "OpenCode Plugin for SonarQube integration - Enterprise-level code quality from the start",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"homepage": "https://github.com/mguttmann/opencode-sonarqube#readme",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@opencode-ai/plugin": "^1.1.34",
|
|
41
|
-
"opencode-sonarqube": "
|
|
41
|
+
"opencode-sonarqube": "^2.1.3",
|
|
42
42
|
"zod": "^3.24.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|