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.
Files changed (2) hide show
  1. package/dist/index.js +114 -38
  2. 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 handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
20452
- const { api: api2, projectKey } = ctx;
20453
- const validResolutions = ["SAFE", "FIXED", "ACKNOWLEDGED"];
20454
- const res = resolution?.toUpperCase() ?? "SAFE";
20455
- if (!validResolutions.includes(res)) {
20456
- return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
20457
- }
20458
- const adminApi = createAdminAPI(ctx);
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 result = await adminApi.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
20465
- let output = `## Hotspot Bulk Review Complete
20501
+ const msg = error45 instanceof Error ? error45.message : String(error45);
20502
+ return `**Error connecting to SonarQube:** ${msg}
20466
20503
 
20467
- **Project:** \`${projectKey}\`
20468
- **Resolution:** ${res}
20469
- **Reviewed:** ${result.success} hotspots
20470
- **Failed:** ${result.failed} hotspots
20471
- `;
20472
- if (result.errors.length > 0) {
20473
- output += `
20474
- ### Errors
20475
- `;
20476
- for (const err of result.errors.slice(0, 10)) {
20477
- output += `- ${err}
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
- return `## Hotspot Reviewed
20488
-
20489
- **Hotspot:** \`${hotspotKey}\`
20490
- **Status:** REVIEWED
20491
- **Resolution:** ${res}
20492
- ${comment ? `**Comment:** ${comment}` : ""}
20493
-
20494
- > Run \`sonarqube({ action: "hotspots" })\` to see remaining hotspots.`;
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.2",
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": "latest",
41
+ "opencode-sonarqube": "^2.1.3",
42
42
  "zod": "^3.24.0"
43
43
  },
44
44
  "devDependencies": {