opencode-sonarqube 2.1.1 → 2.1.3
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 +122 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19313,6 +19313,10 @@ class SonarQubeAPI {
|
|
|
19313
19313
|
};
|
|
19314
19314
|
}
|
|
19315
19315
|
}
|
|
19316
|
+
function createSonarQubeAPIWithCredentials(url2, user, password, logger4) {
|
|
19317
|
+
const client = createClientWithCredentials(url2, user, password, logger4?.child("client"));
|
|
19318
|
+
return new SonarQubeAPI(client, logger4);
|
|
19319
|
+
}
|
|
19316
19320
|
function createSonarQubeAPIWithToken(url2, token, logger4) {
|
|
19317
19321
|
const client = createClientWithToken(url2, token, logger4?.child("client"));
|
|
19318
19322
|
return new SonarQubeAPI(client, logger4);
|
|
@@ -20374,6 +20378,50 @@ Please fix the failed checks before proceeding.`;
|
|
|
20374
20378
|
return output;
|
|
20375
20379
|
}
|
|
20376
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.`;
|
|
20416
|
+
function createAdminAPI(ctx) {
|
|
20417
|
+
return createSonarQubeAPIWithCredentials(ctx.config.url, ctx.config.user, ctx.config.password);
|
|
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
|
+
}
|
|
20377
20425
|
async function handleHotspots(ctx) {
|
|
20378
20426
|
const { api: api2, projectKey } = ctx;
|
|
20379
20427
|
const hotspots = await api2.issues.getSecurityHotspots(projectKey);
|
|
@@ -20441,56 +20489,92 @@ sonarqube({ action: "reviewhotspot", resolution: "SAFE", comment: "Bulk review:
|
|
|
20441
20489
|
}
|
|
20442
20490
|
return output;
|
|
20443
20491
|
}
|
|
20444
|
-
async function
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20449
|
-
|
|
20450
|
-
|
|
20451
|
-
|
|
20452
|
-
const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
20453
|
-
if (toReview.length === 0) {
|
|
20454
|
-
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;
|
|
20455
20500
|
}
|
|
20456
|
-
const
|
|
20457
|
-
|
|
20501
|
+
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
20502
|
+
return `**Error connecting to SonarQube:** ${msg}
|
|
20458
20503
|
|
|
20459
|
-
|
|
20460
|
-
|
|
20461
|
-
|
|
20462
|
-
|
|
20463
|
-
|
|
20464
|
-
|
|
20465
|
-
|
|
20466
|
-
|
|
20467
|
-
|
|
20468
|
-
|
|
20469
|
-
|
|
20470
|
-
|
|
20471
|
-
|
|
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}`);
|
|
20472
20531
|
}
|
|
20473
|
-
output += `
|
|
20474
|
-
> Run \`sonarqube({ action: "status" })\` to check the updated Quality Gate.`;
|
|
20475
|
-
return output;
|
|
20476
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) {
|
|
20477
20538
|
try {
|
|
20478
|
-
await
|
|
20479
|
-
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20483
|
-
**
|
|
20484
|
-
|
|
20485
|
-
|
|
20486
|
-
|
|
20539
|
+
await adminApi.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
|
|
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
|
+
`);
|
|
20487
20553
|
} catch (error45) {
|
|
20554
|
+
if (isAuthError(error45)) {
|
|
20555
|
+
return HOTSPOT_AUTH_ERROR;
|
|
20556
|
+
}
|
|
20488
20557
|
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
20489
20558
|
return `**Error reviewing hotspot:** ${msg}
|
|
20490
20559
|
|
|
20491
20560
|
Make sure the hotspot key is correct. Use \`sonarqube({ action: "hotspots" })\` to list hotspot keys.`;
|
|
20492
20561
|
}
|
|
20493
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
|
+
}
|
|
20494
20578
|
// src/tools/handlers/duplications.ts
|
|
20495
20579
|
async function handleDuplications(ctx) {
|
|
20496
20580
|
const { api: api2, projectKey } = ctx;
|