opencode-sonarqube 2.0.3 → 2.1.0
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 +146 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
3
18
|
var __export = (target, all) => {
|
|
4
19
|
for (var name in all)
|
|
5
20
|
__defProp(target, name, {
|
|
@@ -17808,6 +17823,36 @@ class IssuesAPI extends BaseAPI {
|
|
|
17808
17823
|
const hotspots = await this.getSecurityHotspots(projectKey);
|
|
17809
17824
|
return hotspots.filter((h) => h.status === "TO_REVIEW");
|
|
17810
17825
|
}
|
|
17826
|
+
async reviewHotspot(hotspotKey, status, resolution, comment) {
|
|
17827
|
+
this.logger.info(`Reviewing hotspot ${hotspotKey}: status=${status}, resolution=${resolution}`);
|
|
17828
|
+
const body = {
|
|
17829
|
+
hotspot: hotspotKey,
|
|
17830
|
+
status
|
|
17831
|
+
};
|
|
17832
|
+
if (resolution && status === "REVIEWED") {
|
|
17833
|
+
body.resolution = resolution;
|
|
17834
|
+
}
|
|
17835
|
+
if (comment) {
|
|
17836
|
+
body.comment = comment;
|
|
17837
|
+
}
|
|
17838
|
+
await this.client.post("/api/hotspots/change_status", body);
|
|
17839
|
+
this.logger.info(`Hotspot ${hotspotKey} reviewed successfully`);
|
|
17840
|
+
}
|
|
17841
|
+
async bulkReviewHotspots(hotspotKeys, resolution, comment) {
|
|
17842
|
+
let success2 = 0;
|
|
17843
|
+
let failed = 0;
|
|
17844
|
+
const errors4 = [];
|
|
17845
|
+
for (const key of hotspotKeys) {
|
|
17846
|
+
try {
|
|
17847
|
+
await this.reviewHotspot(key, "REVIEWED", resolution, comment);
|
|
17848
|
+
success2++;
|
|
17849
|
+
} catch (error45) {
|
|
17850
|
+
failed++;
|
|
17851
|
+
errors4.push(`${key}: ${error45 instanceof Error ? error45.message : String(error45)}`);
|
|
17852
|
+
}
|
|
17853
|
+
}
|
|
17854
|
+
return { success: success2, failed, errors: errors4 };
|
|
17855
|
+
}
|
|
17811
17856
|
}
|
|
17812
17857
|
// src/api/quality-gate/api.ts
|
|
17813
17858
|
init_types2();
|
|
@@ -20338,38 +20383,103 @@ async function handleHotspots(ctx) {
|
|
|
20338
20383
|
output += `### Hotspots Requiring Review
|
|
20339
20384
|
|
|
20340
20385
|
`;
|
|
20341
|
-
output += `| Risk | File | Line | Issue |
|
|
20386
|
+
output += `| # | Risk | Key | File | Line | Issue |
|
|
20342
20387
|
`;
|
|
20343
|
-
output +=
|
|
20388
|
+
output += `|---|------|-----|------|------|-------|
|
|
20344
20389
|
`;
|
|
20345
|
-
for (
|
|
20390
|
+
for (let i = 0;i < Math.min(toReview.length, 50); i++) {
|
|
20391
|
+
const hotspot = toReview[i];
|
|
20346
20392
|
const risk = hotspot.vulnerabilityProbability;
|
|
20347
20393
|
const file2 = hotspot.component.split(":").pop() ?? hotspot.component;
|
|
20348
20394
|
const line = hotspot.line ?? "?";
|
|
20349
|
-
const msg = hotspot.message.length >
|
|
20350
|
-
output += `| ${risk} | ${file2} | ${line} | ${msg} |
|
|
20395
|
+
const msg = hotspot.message.length > 50 ? hotspot.message.substring(0, 47) + "..." : hotspot.message;
|
|
20396
|
+
output += `| ${i + 1} | ${risk} | \`${hotspot.key.substring(0, 8)}\` | ${file2} | ${line} | ${msg} |
|
|
20351
20397
|
`;
|
|
20352
20398
|
}
|
|
20353
|
-
if (toReview.length >
|
|
20399
|
+
if (toReview.length > 50) {
|
|
20354
20400
|
output += `
|
|
20355
|
-
*... and ${toReview.length -
|
|
20401
|
+
*... and ${toReview.length - 50} more hotspots*
|
|
20356
20402
|
`;
|
|
20357
20403
|
}
|
|
20358
20404
|
output += `
|
|
20359
|
-
### Review
|
|
20405
|
+
### How to Review Hotspots
|
|
20406
|
+
|
|
20407
|
+
You can review hotspots directly using this tool:
|
|
20408
|
+
|
|
20409
|
+
**Review a single hotspot:**
|
|
20410
|
+
\`\`\`
|
|
20411
|
+
sonarqube({ action: "reviewhotspot", hotspotKey: "<key>", resolution: "SAFE", comment: "Reviewed: no security risk because..." })
|
|
20412
|
+
\`\`\`
|
|
20360
20413
|
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20414
|
+
**Review ALL pending hotspots as SAFE:**
|
|
20415
|
+
\`\`\`
|
|
20416
|
+
sonarqube({ action: "reviewhotspot", resolution: "SAFE", comment: "Bulk review: all hotspots verified safe" })
|
|
20417
|
+
\`\`\`
|
|
20364
20418
|
|
|
20365
|
-
|
|
20366
|
-
-
|
|
20367
|
-
-
|
|
20368
|
-
-
|
|
20419
|
+
**Resolutions:**
|
|
20420
|
+
- \`SAFE\` — The code is secure despite the warning (most common)
|
|
20421
|
+
- \`FIXED\` — The vulnerability has been remediated
|
|
20422
|
+
- \`ACKNOWLEDGED\` — Known risk, accepted
|
|
20423
|
+
|
|
20424
|
+
### Review Guidelines
|
|
20425
|
+
|
|
20426
|
+
1. **HIGH risk**: Review the code carefully — potential security vulnerability
|
|
20427
|
+
2. **MEDIUM risk**: Check regex patterns, randomness, protocol usage
|
|
20428
|
+
3. **LOW risk**: Usually informational — hardcoded IPs, HTTP URLs in non-production code
|
|
20369
20429
|
`;
|
|
20370
20430
|
}
|
|
20371
20431
|
return output;
|
|
20372
20432
|
}
|
|
20433
|
+
async function handleReviewHotspot(ctx, hotspotKey, resolution, comment) {
|
|
20434
|
+
const { api: api2, projectKey } = ctx;
|
|
20435
|
+
const validResolutions = ["SAFE", "FIXED", "ACKNOWLEDGED"];
|
|
20436
|
+
const res = resolution?.toUpperCase() ?? "SAFE";
|
|
20437
|
+
if (!validResolutions.includes(res)) {
|
|
20438
|
+
return `**Error:** Invalid resolution "${resolution}". Must be one of: SAFE, FIXED, ACKNOWLEDGED`;
|
|
20439
|
+
}
|
|
20440
|
+
if (!hotspotKey) {
|
|
20441
|
+
const toReview = await api2.issues.getSecurityHotspotsToReview(projectKey);
|
|
20442
|
+
if (toReview.length === 0) {
|
|
20443
|
+
return formatSuccess("Review Hotspots", "No pending hotspots to review. All hotspots have already been reviewed.");
|
|
20444
|
+
}
|
|
20445
|
+
const result = await api2.issues.bulkReviewHotspots(toReview.map((h) => h.key), res, comment ?? `Bulk reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20446
|
+
let output = `## Hotspot Bulk Review Complete
|
|
20447
|
+
|
|
20448
|
+
**Project:** \`${projectKey}\`
|
|
20449
|
+
**Resolution:** ${res}
|
|
20450
|
+
**Reviewed:** ${result.success} hotspots
|
|
20451
|
+
**Failed:** ${result.failed} hotspots
|
|
20452
|
+
`;
|
|
20453
|
+
if (result.errors.length > 0) {
|
|
20454
|
+
output += `
|
|
20455
|
+
### Errors
|
|
20456
|
+
`;
|
|
20457
|
+
for (const err of result.errors.slice(0, 10)) {
|
|
20458
|
+
output += `- ${err}
|
|
20459
|
+
`;
|
|
20460
|
+
}
|
|
20461
|
+
}
|
|
20462
|
+
output += `
|
|
20463
|
+
> Run \`sonarqube({ action: "status" })\` to check the updated Quality Gate.`;
|
|
20464
|
+
return output;
|
|
20465
|
+
}
|
|
20466
|
+
try {
|
|
20467
|
+
await api2.issues.reviewHotspot(hotspotKey, "REVIEWED", res, comment ?? `Reviewed as ${res} via opencode-sonarqube plugin`);
|
|
20468
|
+
return `## Hotspot Reviewed
|
|
20469
|
+
|
|
20470
|
+
**Hotspot:** \`${hotspotKey}\`
|
|
20471
|
+
**Status:** REVIEWED
|
|
20472
|
+
**Resolution:** ${res}
|
|
20473
|
+
${comment ? `**Comment:** ${comment}` : ""}
|
|
20474
|
+
|
|
20475
|
+
> Run \`sonarqube({ action: "hotspots" })\` to see remaining hotspots.`;
|
|
20476
|
+
} catch (error45) {
|
|
20477
|
+
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
20478
|
+
return `**Error reviewing hotspot:** ${msg}
|
|
20479
|
+
|
|
20480
|
+
Make sure the hotspot key is correct. Use \`sonarqube({ action: "hotspots" })\` to list hotspot keys.`;
|
|
20481
|
+
}
|
|
20482
|
+
}
|
|
20373
20483
|
// src/tools/handlers/duplications.ts
|
|
20374
20484
|
async function handleDuplications(ctx) {
|
|
20375
20485
|
const { api: api2, projectKey } = ctx;
|
|
@@ -20496,14 +20606,17 @@ Run \`sonarqube({ action: "analyze" })\` to generate metrics.`));
|
|
|
20496
20606
|
// src/tools/sonarqube.ts
|
|
20497
20607
|
var logger10 = new Logger("sonarqube-tool");
|
|
20498
20608
|
var SonarQubeToolArgsSchema = exports_external2.object({
|
|
20499
|
-
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)"),
|
|
20609
|
+
action: exports_external2.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "reviewhotspot", "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)"),
|
|
20500
20610
|
scope: exports_external2.enum(["all", "new", "changed"]).optional().default("all").describe("Scope of analysis: all files, only new code, or changed files"),
|
|
20501
20611
|
severity: exports_external2.enum(["blocker", "critical", "major", "minor", "info", "all"]).optional().default("all").describe("Filter issues by minimum severity level"),
|
|
20502
20612
|
fix: exports_external2.boolean().optional().default(false).describe("If true, include fix suggestions in the response"),
|
|
20503
20613
|
projectKey: exports_external2.string().optional().describe("Override the project key (usually auto-detected)"),
|
|
20504
20614
|
force: exports_external2.boolean().optional().default(false).describe("Force re-initialization even if already set up"),
|
|
20505
20615
|
ruleKey: exports_external2.string().optional().describe("Rule key to explain (e.g., 'typescript:S1234') - required for 'rule' action"),
|
|
20506
|
-
branch: exports_external2.string().optional().describe("Branch name for multi-branch analysis (default: main branch)")
|
|
20616
|
+
branch: exports_external2.string().optional().describe("Branch name for multi-branch analysis (default: main branch)"),
|
|
20617
|
+
hotspotKey: exports_external2.string().optional().describe("Hotspot key to review (UUID). If omitted with 'reviewhotspot' action, all TO_REVIEW hotspots are bulk-reviewed."),
|
|
20618
|
+
resolution: exports_external2.enum(["SAFE", "FIXED", "ACKNOWLEDGED"]).optional().describe("Hotspot review resolution: SAFE (no risk), FIXED (remediated), ACKNOWLEDGED (accepted risk)"),
|
|
20619
|
+
comment: exports_external2.string().optional().describe("Review comment explaining the decision")
|
|
20507
20620
|
});
|
|
20508
20621
|
async function executeSonarQubeTool(args, context) {
|
|
20509
20622
|
const directory = context.directory ?? process.cwd();
|
|
@@ -20556,6 +20669,8 @@ ${setupResult.message}`);
|
|
|
20556
20669
|
return await handleValidate(ctx);
|
|
20557
20670
|
case "hotspots":
|
|
20558
20671
|
return await handleHotspots(ctx);
|
|
20672
|
+
case "reviewhotspot":
|
|
20673
|
+
return await handleReviewHotspot(ctx, args.hotspotKey, args.resolution, args.comment);
|
|
20559
20674
|
case "duplications":
|
|
20560
20675
|
return await handleDuplications(ctx);
|
|
20561
20676
|
case "rule":
|
|
@@ -21263,6 +21378,7 @@ Actions:
|
|
|
21263
21378
|
- status: Get quality gate status and metrics
|
|
21264
21379
|
- validate: Check if project meets enterprise quality standards
|
|
21265
21380
|
- hotspots: Get security hotspots that need review
|
|
21381
|
+
- reviewhotspot: Review/resolve security hotspots (mark as SAFE, FIXED, or ACKNOWLEDGED)
|
|
21266
21382
|
- duplications: Find code duplications across the project
|
|
21267
21383
|
- rule: Explain a specific SonarQube rule
|
|
21268
21384
|
- history: Show past analysis history
|
|
@@ -21276,16 +21392,22 @@ Example usage:
|
|
|
21276
21392
|
- sonarqube({ action: "newissues" }) - Issues only in your recent changes
|
|
21277
21393
|
- sonarqube({ action: "worstfiles" }) - Files needing most attention
|
|
21278
21394
|
- sonarqube({ action: "issues", severity: "critical" }) - Get critical+ issues
|
|
21279
|
-
- sonarqube({ action: "status" }) - Check quality gate status
|
|
21395
|
+
- sonarqube({ action: "status" }) - Check quality gate status
|
|
21396
|
+
- sonarqube({ action: "hotspots" }) - List security hotspots
|
|
21397
|
+
- sonarqube({ action: "reviewhotspot", resolution: "SAFE" }) - Bulk-review all hotspots as SAFE
|
|
21398
|
+
- sonarqube({ action: "reviewhotspot", hotspotKey: "abc123", resolution: "SAFE", comment: "No risk" }) - Review single hotspot`,
|
|
21280
21399
|
args: {
|
|
21281
|
-
action: tool.schema.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform"),
|
|
21400
|
+
action: tool.schema.enum(["analyze", "issues", "newissues", "status", "init", "setup", "validate", "hotspots", "reviewhotspot", "duplications", "rule", "history", "profile", "branches", "metrics", "worstfiles"]).describe("Action to perform"),
|
|
21282
21401
|
scope: tool.schema.enum(["all", "new", "changed"]).optional().describe("Scope of analysis"),
|
|
21283
21402
|
severity: tool.schema.enum(["blocker", "critical", "major", "minor", "info", "all"]).optional().describe("Filter issues by minimum severity"),
|
|
21284
21403
|
fix: tool.schema.boolean().optional().describe("Include fix suggestions in response"),
|
|
21285
21404
|
projectKey: tool.schema.string().optional().describe("Override project key"),
|
|
21286
21405
|
force: tool.schema.boolean().optional().describe("Force re-initialization"),
|
|
21287
21406
|
ruleKey: tool.schema.string().optional().describe("Rule key to explain (e.g., 'typescript:S1234')"),
|
|
21288
|
-
branch: tool.schema.string().optional().describe("Branch name for multi-branch projects")
|
|
21407
|
+
branch: tool.schema.string().optional().describe("Branch name for multi-branch projects"),
|
|
21408
|
+
hotspotKey: tool.schema.string().optional().describe("Hotspot key (UUID) to review. If omitted with reviewhotspot action, all pending hotspots are bulk-reviewed."),
|
|
21409
|
+
resolution: tool.schema.string().optional().describe("Hotspot review resolution: SAFE, FIXED, or ACKNOWLEDGED"),
|
|
21410
|
+
comment: tool.schema.string().optional().describe("Review comment explaining the decision")
|
|
21289
21411
|
},
|
|
21290
21412
|
async execute(args, _ctx) {
|
|
21291
21413
|
const result = await executeSonarQubeTool({
|
|
@@ -21296,7 +21418,10 @@ Example usage:
|
|
|
21296
21418
|
projectKey: args.projectKey,
|
|
21297
21419
|
force: args.force ?? false,
|
|
21298
21420
|
ruleKey: args.ruleKey,
|
|
21299
|
-
branch: args.branch
|
|
21421
|
+
branch: args.branch,
|
|
21422
|
+
hotspotKey: args.hotspotKey,
|
|
21423
|
+
resolution: args.resolution,
|
|
21424
|
+
comment: args.comment
|
|
21300
21425
|
}, {
|
|
21301
21426
|
directory: getDirectory(),
|
|
21302
21427
|
config: pluginConfig
|