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.
Files changed (2) hide show
  1. package/dist/index.js +122 -38
  2. 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 handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
20445
- const { api: api2, projectKey } = ctx;
20446
- const validResolutions = ["SAFE", "FIXED", "ACKNOWLEDGED"];
20447
- const res = resolution?.toUpperCase() ?? "SAFE";
20448
- if (!validResolutions.includes(res)) {
20449
- return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
20450
- }
20451
- if (!hotspotKey) {
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 result = await api2.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
20457
- let output = `## Hotspot Bulk Review Complete
20501
+ const msg = error45 instanceof Error ? error45.message : String(error45);
20502
+ return `**Error connecting to SonarQube:** ${msg}
20458
20503
 
20459
- **Project:** \`${projectKey}\`
20460
- **Resolution:** ${res}
20461
- **Reviewed:** ${result.success} hotspots
20462
- **Failed:** ${result.failed} hotspots
20463
- `;
20464
- if (result.errors.length > 0) {
20465
- output += `
20466
- ### Errors
20467
- `;
20468
- for (const err of result.errors.slice(0, 10)) {
20469
- output += `- ${err}
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 api2.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
20479
- return `## Hotspot Reviewed
20480
-
20481
- **Hotspot:** \`${hotspotKey}\`
20482
- **Status:** REVIEWED
20483
- **Resolution:** ${res}
20484
- ${comment ? `**Comment:** ${comment}` : ""}
20485
-
20486
- > Run \`sonarqube({ action: "hotspots" })\` to see remaining hotspots.`;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sonarqube",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
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",