sonar-sweep 0.1.0 → 0.2.1

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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.d.mts +4 -0
  3. package/dist/cli/index.mjs +3 -0
  4. package/dist/cli-BMrnoS5Q.mjs +650 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.mjs +14 -0
  7. package/dist/index.d.mts +263 -0
  8. package/dist/index.mjs +3 -0
  9. package/dist/issue-transition-B5r9hQVC.mjs +338 -0
  10. package/package.json +36 -11
  11. package/dist/cli/commands/issue-accept.d.ts +0 -13
  12. package/dist/cli/commands/issue-accept.js +0 -49
  13. package/dist/cli/commands/issue-accept.js.map +0 -1
  14. package/dist/cli/commands/pr-coverage.d.ts +0 -16
  15. package/dist/cli/commands/pr-coverage.js +0 -79
  16. package/dist/cli/commands/pr-coverage.js.map +0 -1
  17. package/dist/cli/commands/pr-issues.d.ts +0 -15
  18. package/dist/cli/commands/pr-issues.js +0 -78
  19. package/dist/cli/commands/pr-issues.js.map +0 -1
  20. package/dist/cli/commands/pr-report.d.ts +0 -13
  21. package/dist/cli/commands/pr-report.js +0 -94
  22. package/dist/cli/commands/pr-report.js.map +0 -1
  23. package/dist/cli/commands/pr-review.d.ts +0 -16
  24. package/dist/cli/commands/pr-review.js +0 -93
  25. package/dist/cli/commands/pr-review.js.map +0 -1
  26. package/dist/cli/index.d.ts +0 -1
  27. package/dist/cli/index.js +0 -28
  28. package/dist/cli/index.js.map +0 -1
  29. package/dist/cli/project-key.d.ts +0 -1
  30. package/dist/cli/project-key.js +0 -32
  31. package/dist/cli/project-key.js.map +0 -1
  32. package/dist/cli.d.ts +0 -2
  33. package/dist/cli.js +0 -8
  34. package/dist/cli.js.map +0 -1
  35. package/dist/index.d.ts +0 -6
  36. package/dist/index.js +0 -7
  37. package/dist/index.js.map +0 -1
  38. package/dist/service/issue-transition.d.ts +0 -12
  39. package/dist/service/issue-transition.js +0 -20
  40. package/dist/service/issue-transition.js.map +0 -1
  41. package/dist/service/pr-coverage.d.ts +0 -22
  42. package/dist/service/pr-coverage.js +0 -48
  43. package/dist/service/pr-coverage.js.map +0 -1
  44. package/dist/service/pr-issues.d.ts +0 -29
  45. package/dist/service/pr-issues.js +0 -45
  46. package/dist/service/pr-issues.js.map +0 -1
  47. package/dist/service/pr-report.d.ts +0 -27
  48. package/dist/service/pr-report.js +0 -59
  49. package/dist/service/pr-report.js.map +0 -1
  50. package/dist/service/pr-review.d.ts +0 -39
  51. package/dist/service/pr-review.js +0 -81
  52. package/dist/service/pr-review.js.map +0 -1
  53. package/dist/service/sonarcloud-client.d.ts +0 -100
  54. package/dist/service/sonarcloud-client.js +0 -112
  55. package/dist/service/sonarcloud-client.js.map +0 -1
package/README.md CHANGED
@@ -33,7 +33,7 @@ sonar-sweep pr-report <projectKey> <pullRequest> --token <token>
33
33
  Or set environment variables:
34
34
 
35
35
  ```bash
36
- SONAR_TOKEN=...
36
+ SONAR_TOKEN=...
37
37
  SONAR_BASE_URL=https://sonarcloud.io
38
38
  ```
39
39
 
@@ -0,0 +1,4 @@
1
+ //#region src/cli/index.d.ts
2
+ declare function run(argv?: Array<string>): Promise<void>;
3
+ //#endregion
4
+ export { run };
@@ -0,0 +1,3 @@
1
+ import { t as run } from "../cli-BMrnoS5Q.mjs";
2
+
3
+ export { run };
@@ -0,0 +1,650 @@
1
+ import { a as getPullRequestReport, i as getPullRequestIssues, n as getPullRequestReview, o as SonarCloudClient, r as getPullRequestCoverage, t as transitionIssue } from "./issue-transition-B5r9hQVC.mjs";
2
+ import { hideBin } from "yargs/helpers";
3
+ import yargs from "yargs/yargs";
4
+ import { execFileSync } from "node:child_process";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ //#region \0rolldown/runtime.js
9
+ var __defProp = Object.defineProperty;
10
+ var __exportAll = (all, no_symbols) => {
11
+ let target = {};
12
+ for (var name in all) {
13
+ __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ }
18
+ if (!no_symbols) {
19
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
20
+ }
21
+ return target;
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/service/hotspot-transition.ts
26
+ async function reviewHotspot(clientOptions, input) {
27
+ const hotspotKey = input.hotspotKey.trim();
28
+ const resolution = input.resolution;
29
+ const comment = input.comment?.trim();
30
+ if (!hotspotKey) throw new Error("Missing hotspotKey");
31
+ if (!resolution) throw new Error("Missing resolution (SAFE, ACKNOWLEDGED, or FIXED)");
32
+ const validResolutions = [
33
+ "SAFE",
34
+ "ACKNOWLEDGED",
35
+ "FIXED"
36
+ ];
37
+ if (!validResolutions.includes(resolution)) throw new Error(`Invalid resolution: ${resolution}. Must be one of: ${validResolutions.join(", ")}`);
38
+ await new SonarCloudClient(clientOptions).changeHotspotStatus(hotspotKey, "REVIEWED", resolution, comment);
39
+ return {
40
+ applied: true,
41
+ hotspotKey,
42
+ resolution,
43
+ status: "REVIEWED"
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/cli/commands/hotspot-review.ts
49
+ var hotspot_review_exports = /* @__PURE__ */ __exportAll({
50
+ builder: () => builder$6,
51
+ command: () => command$6,
52
+ describe: () => describe$6,
53
+ handler: () => handler$6
54
+ });
55
+ const command$6 = "hotspot-review <hotspotKey>";
56
+ const describe$6 = "Mark a security hotspot as reviewed (SAFE, ACKNOWLEDGED, or FIXED)";
57
+ function builder$6(yargs) {
58
+ return yargs.positional("hotspotKey", {
59
+ coerce: (value) => String(value).trim(),
60
+ demandOption: "Provide a hotspot key",
61
+ type: "string"
62
+ }).option("token", {
63
+ default: process.env.SONAR_TOKEN,
64
+ defaultDescription: "SONAR_TOKEN",
65
+ demandOption: "Provide --token or set SONAR_TOKEN",
66
+ type: "string"
67
+ }).option("baseUrl", {
68
+ alias: ["base-url", "url"],
69
+ coerce: (value) => String(value).replace(/\/$/, ""),
70
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
71
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
72
+ type: "string"
73
+ }).option("resolution", {
74
+ alias: "r",
75
+ choices: [
76
+ "SAFE",
77
+ "ACKNOWLEDGED",
78
+ "FIXED"
79
+ ],
80
+ default: "SAFE",
81
+ describe: "Resolution type: SAFE (not a risk), ACKNOWLEDGED (risk accepted), FIXED (code changed)",
82
+ type: "string"
83
+ }).option("comment", {
84
+ alias: "c",
85
+ describe: "Optional review comment",
86
+ type: "string"
87
+ }).option("json", {
88
+ default: false,
89
+ describe: "Print raw JSON for automation/agents",
90
+ type: "boolean"
91
+ });
92
+ }
93
+ async function handler$6(args) {
94
+ const result = await reviewHotspot({
95
+ baseUrl: args.baseUrl,
96
+ token: args.token
97
+ }, {
98
+ comment: args.comment,
99
+ hotspotKey: args.hotspotKey,
100
+ resolution: args.resolution
101
+ });
102
+ if (args.json) {
103
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
104
+ return;
105
+ }
106
+ process.stdout.write(`Reviewed hotspot ${result.hotspotKey} as ${result.resolution}\n`);
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/cli/commands/issue-accept.ts
111
+ var issue_accept_exports = /* @__PURE__ */ __exportAll({
112
+ builder: () => builder$5,
113
+ command: () => command$5,
114
+ describe: () => describe$5,
115
+ handler: () => handler$5
116
+ });
117
+ const command$5 = "issue-accept <issueKey>";
118
+ const describe$5 = "Mark a Sonar issue as accepted (transition=accept)";
119
+ function builder$5(yargs) {
120
+ return yargs.positional("issueKey", {
121
+ coerce: (value) => String(value).trim(),
122
+ demandOption: "Provide an issue key",
123
+ type: "string"
124
+ }).option("token", {
125
+ default: process.env.SONAR_TOKEN,
126
+ defaultDescription: "SONAR_TOKEN",
127
+ demandOption: "Provide --token or set SONAR_TOKEN",
128
+ type: "string"
129
+ }).option("baseUrl", {
130
+ alias: ["base-url", "url"],
131
+ coerce: (value) => String(value).replace(/\/$/, ""),
132
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
133
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
134
+ type: "string"
135
+ }).option("comment", {
136
+ describe: "Optional acceptance comment",
137
+ type: "string"
138
+ }).option("json", {
139
+ default: false,
140
+ describe: "Print raw JSON for automation/agents",
141
+ type: "boolean"
142
+ });
143
+ }
144
+ async function handler$5(args) {
145
+ const result = await transitionIssue({
146
+ baseUrl: args.baseUrl,
147
+ token: args.token
148
+ }, {
149
+ comment: args.comment,
150
+ issueKey: args.issueKey,
151
+ transition: "accept"
152
+ });
153
+ if (args.json) {
154
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
155
+ return;
156
+ }
157
+ process.stdout.write(`Accepted issue ${result.issueKey} (${result.transition})\n`);
158
+ }
159
+
160
+ //#endregion
161
+ //#region src/cli/project-key.ts
162
+ function resolveProjectKey(projectKey) {
163
+ if (projectKey?.trim()) return projectKey.trim();
164
+ const sonarPropsPath = join(getGitRoot(), "sonar-project.properties");
165
+ if (!existsSync(sonarPropsPath)) throw new Error(`Missing projectKey and no sonar-project.properties found at ${sonarPropsPath}. Provide --projectKey explicitly.`);
166
+ const key = readFileSync(sonarPropsPath, "utf8").match(/^\s*sonar\.projectKey\s*=\s*(.+)\s*$/m)?.[1]?.trim();
167
+ if (!key) throw new Error(`Could not read sonar.projectKey from ${sonarPropsPath}. Provide --projectKey explicitly.`);
168
+ return key;
169
+ }
170
+ function getGitRoot() {
171
+ try {
172
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
173
+ encoding: "utf8",
174
+ stdio: [
175
+ "ignore",
176
+ "pipe",
177
+ "ignore"
178
+ ]
179
+ }).trim();
180
+ } catch {
181
+ return process.cwd();
182
+ }
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/cli/commands/pr-coverage.ts
187
+ var pr_coverage_exports = /* @__PURE__ */ __exportAll({
188
+ builder: () => builder$4,
189
+ command: () => command$4,
190
+ describe: () => describe$4,
191
+ handler: () => handler$4
192
+ });
193
+ const command$4 = "pr-coverage <pullRequest> [projectKey]";
194
+ const describe$4 = "List files with low new-code coverage for a pull request";
195
+ function builder$4(yargs) {
196
+ return yargs.positional("projectKey", {
197
+ describe: "Sonar project key (optional; auto-detected from sonar-project.properties)",
198
+ type: "string"
199
+ }).positional("pullRequest", {
200
+ coerce: (value) => String(value).trim(),
201
+ demandOption: "Provide a pull request key/id",
202
+ type: "string"
203
+ }).option("token", {
204
+ default: process.env.SONAR_TOKEN,
205
+ defaultDescription: "SONAR_TOKEN",
206
+ demandOption: "Provide --token or set SONAR_TOKEN",
207
+ type: "string"
208
+ }).option("baseUrl", {
209
+ alias: ["base-url", "url"],
210
+ coerce: (value) => String(value).replace(/\/$/, ""),
211
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
212
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
213
+ type: "string"
214
+ }).option("projectKey", {
215
+ alias: ["project-key", "k"],
216
+ describe: "Sonar project key (overrides auto-detection)",
217
+ type: "string"
218
+ }).option("threshold", {
219
+ coerce: (value) => Math.max(0, Math.min(100, Number(value))),
220
+ default: 80,
221
+ describe: "Coverage threshold percentage",
222
+ type: "number"
223
+ }).option("includePassing", {
224
+ default: false,
225
+ describe: "Include files that meet threshold too",
226
+ type: "boolean"
227
+ }).option("maxFiles", {
228
+ alias: ["max", "limit"],
229
+ coerce: (value) => Math.max(1, Number(value)),
230
+ default: 20,
231
+ type: "number"
232
+ }).option("json", {
233
+ default: false,
234
+ describe: "Print raw JSON for automation/agents",
235
+ type: "boolean"
236
+ });
237
+ }
238
+ async function handler$4(args) {
239
+ const projectKey = resolveProjectKey(args.projectKey);
240
+ const report = await getPullRequestCoverage({
241
+ baseUrl: args.baseUrl,
242
+ token: args.token
243
+ }, {
244
+ includePassing: args.includePassing,
245
+ maxFiles: args.maxFiles,
246
+ projectKey,
247
+ pullRequest: args.pullRequest,
248
+ threshold: args.threshold
249
+ });
250
+ if (args.json) {
251
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
252
+ return;
253
+ }
254
+ process.stdout.write(`Found ${report.files.length} file(s) below ${report.threshold.toFixed(1)}% new-code coverage\n`);
255
+ for (const file of report.files) process.stdout.write(`- ${file.file}: ${file.coverageOnNewCode.toFixed(1)}% (${file.uncoveredLines}/${file.linesToCover} uncovered lines)\n`);
256
+ process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
257
+ }
258
+
259
+ //#endregion
260
+ //#region src/service/pr-hotspots.ts
261
+ async function getPullRequestHotspots(clientOptions, input) {
262
+ const projectKey = input.projectKey.trim();
263
+ const pullRequest = input.pullRequest.trim();
264
+ const status = input.status ?? "TO_REVIEW";
265
+ const page = input.page ?? 1;
266
+ const pageSize = input.pageSize ?? 100;
267
+ if (!projectKey) throw new Error("Missing projectKey");
268
+ if (!pullRequest) throw new Error("Missing pullRequest");
269
+ const baseUrl = (clientOptions.baseUrl ?? "https://sonarcloud.io").replace(/\/$/, "");
270
+ const response = await new SonarCloudClient(clientOptions).getHotspots(projectKey, pullRequest, status, page, pageSize);
271
+ const componentMap = /* @__PURE__ */ new Map();
272
+ for (const component of response.components ?? []) componentMap.set(component.key, component.path ?? component.longName ?? component.key);
273
+ const hotspots = response.hotspots.map((hotspot) => ({
274
+ file: extractFilePath(hotspot.component, projectKey, componentMap),
275
+ key: hotspot.key,
276
+ line: hotspot.line,
277
+ message: hotspot.message,
278
+ resolution: hotspot.resolution,
279
+ securityCategory: hotspot.securityCategory,
280
+ status: hotspot.status,
281
+ vulnerabilityProbability: hotspot.vulnerabilityProbability
282
+ }));
283
+ return {
284
+ analysisUrl: `${baseUrl}/project/security_hotspots?id=${encodeURIComponent(projectKey)}&pullRequest=${encodeURIComponent(pullRequest)}`,
285
+ hotspots,
286
+ page: response.paging.pageIndex,
287
+ pageSize: response.paging.pageSize,
288
+ projectKey,
289
+ pullRequest,
290
+ total: response.paging.total
291
+ };
292
+ }
293
+ function extractFilePath(componentKey, projectKey, componentMap) {
294
+ const mappedPath = componentMap.get(componentKey);
295
+ if (mappedPath) return mappedPath;
296
+ const prefix = `${projectKey}:`;
297
+ if (componentKey.startsWith(prefix)) return componentKey.slice(prefix.length);
298
+ return componentKey;
299
+ }
300
+
301
+ //#endregion
302
+ //#region src/cli/commands/pr-hotspots.ts
303
+ var pr_hotspots_exports = /* @__PURE__ */ __exportAll({
304
+ builder: () => builder$3,
305
+ command: () => command$3,
306
+ describe: () => describe$3,
307
+ handler: () => handler$3
308
+ });
309
+ const command$3 = "pr-hotspots <pullRequest> [projectKey]";
310
+ const describe$3 = "Fetch security hotspots for a pull request";
311
+ function builder$3(yargs) {
312
+ return yargs.positional("projectKey", {
313
+ describe: "Sonar project key (optional; auto-detected from sonar-project.properties)",
314
+ type: "string"
315
+ }).positional("pullRequest", {
316
+ coerce: (value) => String(value).trim(),
317
+ demandOption: "Provide a pull request key/id",
318
+ type: "string"
319
+ }).option("token", {
320
+ default: process.env.SONAR_TOKEN,
321
+ defaultDescription: "SONAR_TOKEN",
322
+ demandOption: "Provide --token or set SONAR_TOKEN",
323
+ type: "string"
324
+ }).option("baseUrl", {
325
+ alias: ["base-url", "url"],
326
+ coerce: (value) => String(value).replace(/\/$/, ""),
327
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
328
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
329
+ type: "string"
330
+ }).option("projectKey", {
331
+ alias: ["project-key", "k"],
332
+ describe: "Sonar project key (overrides auto-detection)",
333
+ type: "string"
334
+ }).option("status", {
335
+ alias: "s",
336
+ choices: ["TO_REVIEW", "REVIEWED"],
337
+ default: "TO_REVIEW",
338
+ describe: "Filter hotspots by status",
339
+ type: "string"
340
+ }).option("page", {
341
+ alias: "p",
342
+ coerce: (value) => Math.max(1, Number(value)),
343
+ default: 1,
344
+ type: "number"
345
+ }).option("pageSize", {
346
+ alias: ["ps", "page-size"],
347
+ coerce: (value) => Math.min(500, Math.max(1, Number(value))),
348
+ default: 100,
349
+ type: "number"
350
+ }).option("json", {
351
+ default: false,
352
+ describe: "Print raw JSON for automation/agents",
353
+ type: "boolean"
354
+ });
355
+ }
356
+ async function handler$3(args) {
357
+ const projectKey = resolveProjectKey(args.projectKey);
358
+ const result = await getPullRequestHotspots({
359
+ baseUrl: args.baseUrl,
360
+ token: args.token
361
+ }, {
362
+ page: args.page,
363
+ pageSize: args.pageSize,
364
+ projectKey,
365
+ pullRequest: args.pullRequest,
366
+ status: args.status
367
+ });
368
+ if (args.json) {
369
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
370
+ return;
371
+ }
372
+ const statusLabel = args.status === "TO_REVIEW" ? "unreviewed" : "reviewed";
373
+ process.stdout.write(`Found ${result.total} ${statusLabel} security hotspot(s) for PR ${result.pullRequest} (${result.projectKey})\n`);
374
+ if (result.hotspots.length === 0) {
375
+ process.stdout.write(`\nSee analysis details: ${result.analysisUrl}\n`);
376
+ return;
377
+ }
378
+ process.stdout.write("\n");
379
+ for (const hotspot of result.hotspots) {
380
+ const location = hotspot.line ? `${hotspot.file}:${hotspot.line}` : hotspot.file;
381
+ const resolutionInfo = hotspot.resolution ? ` (${hotspot.resolution})` : "";
382
+ process.stdout.write(`[${hotspot.vulnerabilityProbability}] ${hotspot.key}\n ${location}\n ${hotspot.message}${resolutionInfo}\n\n`);
383
+ }
384
+ process.stdout.write(`See analysis details: ${result.analysisUrl}\n`);
385
+ }
386
+
387
+ //#endregion
388
+ //#region src/cli/commands/pr-issues.ts
389
+ var pr_issues_exports = /* @__PURE__ */ __exportAll({
390
+ builder: () => builder$2,
391
+ command: () => command$2,
392
+ describe: () => describe$2,
393
+ handler: () => handler$2
394
+ });
395
+ const command$2 = "pr-issues <pullRequest> [projectKey]";
396
+ const describe$2 = "Fetch SonarQube Cloud issue details for a pull request";
397
+ function builder$2(yargs) {
398
+ return yargs.positional("projectKey", {
399
+ describe: "Sonar project key (optional; auto-detected from sonar-project.properties)",
400
+ type: "string"
401
+ }).positional("pullRequest", {
402
+ coerce: (value) => String(value).trim(),
403
+ demandOption: "Provide a pull request key/id",
404
+ type: "string"
405
+ }).option("token", {
406
+ default: process.env.SONAR_TOKEN,
407
+ defaultDescription: "SONAR_TOKEN",
408
+ demandOption: "Provide --token or set SONAR_TOKEN",
409
+ type: "string"
410
+ }).option("baseUrl", {
411
+ alias: ["base-url", "url"],
412
+ coerce: (value) => String(value).replace(/\/$/, ""),
413
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
414
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
415
+ type: "string"
416
+ }).option("projectKey", {
417
+ alias: ["project-key", "k"],
418
+ describe: "Sonar project key (overrides auto-detection)",
419
+ type: "string"
420
+ }).option("page", {
421
+ alias: "p",
422
+ coerce: (value) => Math.max(1, Number(value)),
423
+ default: 1,
424
+ type: "number"
425
+ }).option("pageSize", {
426
+ alias: ["ps", "page-size"],
427
+ coerce: (value) => Math.min(500, Math.max(1, Number(value))),
428
+ default: 100,
429
+ type: "number"
430
+ }).option("json", {
431
+ default: false,
432
+ describe: "Print raw JSON for automation/agents",
433
+ type: "boolean"
434
+ });
435
+ }
436
+ async function handler$2(args) {
437
+ const projectKey = resolveProjectKey(args.projectKey);
438
+ const report = await getPullRequestIssues({
439
+ baseUrl: args.baseUrl,
440
+ token: args.token
441
+ }, {
442
+ page: args.page,
443
+ pageSize: args.pageSize,
444
+ projectKey,
445
+ pullRequest: args.pullRequest
446
+ });
447
+ if (args.json) {
448
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
449
+ return;
450
+ }
451
+ process.stdout.write(`Found ${report.total} open issue(s) in new code for PR ${report.pullRequest} (${report.projectKey})\n`);
452
+ if (report.issues.length === 0) {
453
+ process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
454
+ return;
455
+ }
456
+ for (const issue of report.issues) {
457
+ const location = issue.line ? `${issue.file}:${issue.line}` : issue.file;
458
+ process.stdout.write(`- [${issue.issueStatus}] ${issue.severity} ${issue.type} ${issue.rule} ${location} - ${issue.message}\n`);
459
+ }
460
+ process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
461
+ }
462
+
463
+ //#endregion
464
+ //#region src/cli/commands/pr-report.ts
465
+ var pr_report_exports = /* @__PURE__ */ __exportAll({
466
+ builder: () => builder$1,
467
+ command: () => command$1,
468
+ describe: () => describe$1,
469
+ handler: () => handler$1
470
+ });
471
+ const command$1 = "pr-report <pullRequest> [projectKey]";
472
+ const describe$1 = "Fetch SonarQube Cloud details for a pull request";
473
+ function builder$1(yargs) {
474
+ return yargs.positional("projectKey", {
475
+ describe: "Sonar project key (optional; auto-detected from sonar-project.properties)",
476
+ type: "string"
477
+ }).positional("pullRequest", {
478
+ coerce: (value) => String(value).trim(),
479
+ demandOption: "Provide a pull request key/id",
480
+ type: "string"
481
+ }).option("token", {
482
+ default: process.env.SONAR_TOKEN,
483
+ defaultDescription: "SONAR_TOKEN",
484
+ demandOption: "Provide --token or set SONAR_TOKEN",
485
+ type: "string"
486
+ }).option("baseUrl", {
487
+ alias: ["base-url", "url"],
488
+ coerce: (value) => String(value).replace(/\/$/, ""),
489
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
490
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
491
+ type: "string"
492
+ }).option("projectKey", {
493
+ alias: ["project-key", "k"],
494
+ describe: "Sonar project key (overrides auto-detection)",
495
+ type: "string"
496
+ }).option("json", {
497
+ default: false,
498
+ describe: "Print raw JSON for automation/agents",
499
+ type: "boolean"
500
+ });
501
+ }
502
+ async function handler$1(args) {
503
+ const projectKey = resolveProjectKey(args.projectKey);
504
+ const report = await getPullRequestReport({
505
+ baseUrl: args.baseUrl,
506
+ token: args.token
507
+ }, {
508
+ projectKey,
509
+ pullRequest: args.pullRequest
510
+ });
511
+ if (args.json) {
512
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
513
+ return;
514
+ }
515
+ const qualityGate = report.qualityGateStatus === "OK" ? "Quality Gate passed" : `Quality Gate failed (${report.qualityGateStatus})`;
516
+ process.stdout.write(`${qualityGate}\n\n`);
517
+ if (report.failingQualityGateConditions.length > 0) {
518
+ process.stdout.write("Quality Gate Conditions\n");
519
+ for (const condition of report.failingQualityGateConditions) {
520
+ const metric = formatMetricLabel(condition.metricKey);
521
+ const threshold = condition.errorThreshold ?? "n/a";
522
+ const actual = condition.actualValue ?? "n/a";
523
+ process.stdout.write(`- ${metric}: actual=${actual}, threshold=${condition.comparator} ${threshold}\n`);
524
+ }
525
+ process.stdout.write("\n");
526
+ }
527
+ process.stdout.write("Issues\n");
528
+ process.stdout.write(`- ${report.issueCounts.newIssues} New issues\n`);
529
+ process.stdout.write(`- ${report.issueCounts.acceptedIssues} Accepted issues\n\n`);
530
+ process.stdout.write("Measures\n");
531
+ process.stdout.write(`- ${report.measures.securityHotspots} Security Hotspots\n`);
532
+ process.stdout.write(`- ${report.measures.coverageOnNewCode.toFixed(1)}% Coverage on New Code\n`);
533
+ process.stdout.write(`- ${report.measures.duplicationOnNewCode.toFixed(1)}% Duplication on New Code\n\n`);
534
+ process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
535
+ }
536
+ function formatMetricLabel(metricKey) {
537
+ switch (metricKey) {
538
+ case "new_coverage": return "Coverage on New Code";
539
+ case "new_duplicated_lines_density": return "Duplication on New Code";
540
+ case "new_security_hotspots_reviewed": return "Security Hotspots Reviewed on New Code";
541
+ case "new_reliability_rating": return "Reliability Rating on New Code";
542
+ case "new_security_rating": return "Security Rating on New Code";
543
+ case "new_maintainability_rating": return "Maintainability Rating on New Code";
544
+ default: return metricKey;
545
+ }
546
+ }
547
+
548
+ //#endregion
549
+ //#region src/cli/commands/pr-review.ts
550
+ var pr_review_exports = /* @__PURE__ */ __exportAll({
551
+ builder: () => builder,
552
+ command: () => command,
553
+ describe: () => describe,
554
+ handler: () => handler
555
+ });
556
+ const command = "pr-review <pullRequest> [projectKey]";
557
+ const describe = "Review Sonar pull request issues with code context snippets";
558
+ function builder(yargs) {
559
+ return yargs.positional("projectKey", {
560
+ describe: "Sonar project key (optional; auto-detected from sonar-project.properties)",
561
+ type: "string"
562
+ }).positional("pullRequest", {
563
+ coerce: (value) => String(value).trim(),
564
+ demandOption: "Provide a pull request key/id",
565
+ type: "string"
566
+ }).option("token", {
567
+ default: process.env.SONAR_TOKEN,
568
+ defaultDescription: "SONAR_TOKEN",
569
+ demandOption: "Provide --token or set SONAR_TOKEN",
570
+ type: "string"
571
+ }).option("baseUrl", {
572
+ alias: ["base-url", "url"],
573
+ coerce: (value) => String(value).replace(/\/$/, ""),
574
+ default: process.env.SONAR_BASE_URL ?? "https://sonarcloud.io",
575
+ defaultDescription: "SONAR_BASE_URL or https://sonarcloud.io",
576
+ type: "string"
577
+ }).option("projectKey", {
578
+ alias: ["project-key", "k"],
579
+ describe: "Sonar project key (overrides auto-detection)",
580
+ type: "string"
581
+ }).option("contextLines", {
582
+ alias: ["context", "C"],
583
+ coerce: (value) => Math.max(0, Number(value)),
584
+ default: 3,
585
+ type: "number"
586
+ }).option("page", {
587
+ alias: "p",
588
+ coerce: (value) => Math.max(1, Number(value)),
589
+ default: 1,
590
+ type: "number"
591
+ }).option("pageSize", {
592
+ alias: ["ps", "page-size"],
593
+ coerce: (value) => Math.min(500, Math.max(1, Number(value))),
594
+ default: 20,
595
+ type: "number"
596
+ }).option("json", {
597
+ default: false,
598
+ describe: "Print raw JSON for automation/agents",
599
+ type: "boolean"
600
+ });
601
+ }
602
+ async function handler(args) {
603
+ const projectKey = resolveProjectKey(args.projectKey);
604
+ const report = await getPullRequestReview({
605
+ baseUrl: args.baseUrl,
606
+ token: args.token
607
+ }, {
608
+ contextLines: args.contextLines,
609
+ page: args.page,
610
+ pageSize: args.pageSize,
611
+ projectKey,
612
+ pullRequest: args.pullRequest
613
+ });
614
+ if (args.json) {
615
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
616
+ return;
617
+ }
618
+ process.stdout.write(`Found ${report.total} open issue(s) in new code for PR ${report.pullRequest} (${report.projectKey})\n\n`);
619
+ for (const issue of report.issues) {
620
+ const location = issue.line ? `${issue.file}:${issue.line}` : issue.file;
621
+ process.stdout.write(`[${issue.issueStatus}] ${issue.severity} ${issue.type} ${issue.rule} ${location}\n${issue.message}\n`);
622
+ process.stdout.write(`Issue URL: ${issue.issueUrl}\n`);
623
+ if (issue.snippet) {
624
+ process.stdout.write("Context:\n");
625
+ for (const line of issue.snippet.lines) {
626
+ const marker = line.highlight ? ">" : " ";
627
+ process.stdout.write(`${marker} ${line.line.toString().padStart(4, " ")} | ${line.text}\n`);
628
+ }
629
+ } else if (issue.sourceError) process.stdout.write(`Context unavailable: ${issue.sourceError}\n`);
630
+ process.stdout.write("\n");
631
+ }
632
+ process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
633
+ }
634
+
635
+ //#endregion
636
+ //#region src/cli/index.ts
637
+ async function run(argv = process.argv) {
638
+ await yargs(hideBin(argv)).scriptName("sonar-sweep").env("SONAR").command(hotspot_review_exports).command(issue_accept_exports).command(pr_coverage_exports).command(pr_hotspots_exports).command(pr_issues_exports).command(pr_report_exports).command(pr_review_exports).demandCommand(1, "Provide a command").strict().help().fail((message, error, yargsInstance) => {
639
+ if (error) {
640
+ process.stderr.write(`${error.message}\n`);
641
+ process.exit(1);
642
+ }
643
+ process.stderr.write(`${message}\n`);
644
+ yargsInstance.showHelp();
645
+ process.exit(1);
646
+ }).parseAsync();
647
+ }
648
+
649
+ //#endregion
650
+ export { run as t };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { t as run } from "./cli-BMrnoS5Q.mjs";
3
+
4
+ //#region src/cli.ts
5
+ try {
6
+ await run(process.argv);
7
+ } catch (error) {
8
+ const message = error instanceof Error ? error.message : String(error);
9
+ process.stderr.write(`${message}\n`);
10
+ process.exit(1);
11
+ }
12
+
13
+ //#endregion
14
+ export { };