sonar-sweep 0.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/README.md +70 -0
- package/dist/cli/commands/issue-accept.d.ts +13 -0
- package/dist/cli/commands/issue-accept.js +49 -0
- package/dist/cli/commands/issue-accept.js.map +1 -0
- package/dist/cli/commands/pr-coverage.d.ts +16 -0
- package/dist/cli/commands/pr-coverage.js +79 -0
- package/dist/cli/commands/pr-coverage.js.map +1 -0
- package/dist/cli/commands/pr-issues.d.ts +15 -0
- package/dist/cli/commands/pr-issues.js +78 -0
- package/dist/cli/commands/pr-issues.js.map +1 -0
- package/dist/cli/commands/pr-report.d.ts +13 -0
- package/dist/cli/commands/pr-report.js +94 -0
- package/dist/cli/commands/pr-report.js.map +1 -0
- package/dist/cli/commands/pr-review.d.ts +16 -0
- package/dist/cli/commands/pr-review.js +93 -0
- package/dist/cli/commands/pr-review.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +28 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/project-key.d.ts +1 -0
- package/dist/cli/project-key.js +32 -0
- package/dist/cli/project-key.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/service/issue-transition.d.ts +12 -0
- package/dist/service/issue-transition.js +20 -0
- package/dist/service/issue-transition.js.map +1 -0
- package/dist/service/pr-coverage.d.ts +22 -0
- package/dist/service/pr-coverage.js +48 -0
- package/dist/service/pr-coverage.js.map +1 -0
- package/dist/service/pr-issues.d.ts +29 -0
- package/dist/service/pr-issues.js +45 -0
- package/dist/service/pr-issues.js.map +1 -0
- package/dist/service/pr-report.d.ts +27 -0
- package/dist/service/pr-report.js +59 -0
- package/dist/service/pr-report.js.map +1 -0
- package/dist/service/pr-review.d.ts +39 -0
- package/dist/service/pr-review.js +81 -0
- package/dist/service/pr-review.js.map +1 -0
- package/dist/service/sonarcloud-client.d.ts +100 -0
- package/dist/service/sonarcloud-client.js +112 -0
- package/dist/service/sonarcloud-client.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# sonar-sweep-cli
|
|
2
|
+
|
|
3
|
+
TypeScript CLI to fetch SonarQube Cloud pull request details for coding-agent workflows.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Fetches Quality Gate status for a pull request
|
|
8
|
+
- Fetches new issue counts and accepted issue counts
|
|
9
|
+
- Fetches open issue details for new code (rule, severity, file, line, message)
|
|
10
|
+
- Fetches per-file new-code coverage gaps for a pull request
|
|
11
|
+
- Fetches issue review data with source snippets for faster triage
|
|
12
|
+
- Applies issue transitions (for example mark issue as accepted)
|
|
13
|
+
- Fetches key new-code measures (security hotspots, coverage, duplication)
|
|
14
|
+
- Prints either human-readable output or JSON (`--json`) for automation
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Authentication
|
|
24
|
+
|
|
25
|
+
The CLI uses SonarQube Cloud tokens via bearer auth.
|
|
26
|
+
|
|
27
|
+
You can pass the token explicitly:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
sonar-sweep pr-report <projectKey> <pullRequest> --token <token>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or set environment variables:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
SONAR_TOKEN=...
|
|
37
|
+
SONAR_BASE_URL=https://sonarcloud.io
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
sonar-sweep pr-report <pullRequest>
|
|
44
|
+
sonar-sweep pr-issues <pullRequest>
|
|
45
|
+
sonar-sweep pr-coverage <pullRequest>
|
|
46
|
+
sonar-sweep pr-review <pullRequest>
|
|
47
|
+
sonar-sweep issue-accept <issueKey>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For `pr-*` commands, the CLI auto-detects `sonar.projectKey` from `sonar-project.properties` in the current git root. You can still override with `--projectKey`.
|
|
51
|
+
|
|
52
|
+
JSON output for agents:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sonar-sweep pr-report <pullRequest> --json
|
|
56
|
+
sonar-sweep pr-issues <pullRequest> --json
|
|
57
|
+
sonar-sweep pr-coverage <pullRequest> --json
|
|
58
|
+
sonar-sweep pr-review <pullRequest> --json
|
|
59
|
+
sonar-sweep issue-accept <issueKey> --comment "Accepted with rationale" --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
node dist/cli.js pr-report 2221 --projectKey sueddeutsche_app-android --json
|
|
66
|
+
node dist/cli.js pr-issues 2220 --projectKey sueddeutsche_app-android --json
|
|
67
|
+
node dist/cli.js pr-coverage 591 --projectKey sueddeutsche_szhp-pages --threshold 80 --json
|
|
68
|
+
node dist/cli.js pr-review 145 --projectKey sueddeutsche_szde-redirect-admin --context 4 --json
|
|
69
|
+
node dist/cli.js issue-accept AZx2EKMtvB1gqnjpRLA4 --comment "Reviewed and accepted" --json
|
|
70
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
type IssueAcceptArgs = {
|
|
3
|
+
token: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
issueKey: string;
|
|
6
|
+
comment?: string;
|
|
7
|
+
json: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const command = "issue-accept <issueKey>";
|
|
10
|
+
export declare const describe = "Mark a Sonar issue as accepted (transition=accept)";
|
|
11
|
+
export declare function builder(yargs: Argv): Argv<IssueAcceptArgs>;
|
|
12
|
+
export declare function handler(args: IssueAcceptArgs): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { transitionIssue } from '../../service/issue-transition.js';
|
|
2
|
+
export const command = 'issue-accept <issueKey>';
|
|
3
|
+
export const describe = 'Mark a Sonar issue as accepted (transition=accept)';
|
|
4
|
+
export function builder(yargs) {
|
|
5
|
+
return yargs
|
|
6
|
+
.positional('issueKey', {
|
|
7
|
+
type: 'string',
|
|
8
|
+
demandOption: 'Provide an issue key',
|
|
9
|
+
coerce: (value) => String(value).trim(),
|
|
10
|
+
})
|
|
11
|
+
.option('token', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: process.env.SONAR_TOKEN,
|
|
14
|
+
defaultDescription: 'SONAR_TOKEN',
|
|
15
|
+
demandOption: 'Provide --token or set SONAR_TOKEN',
|
|
16
|
+
})
|
|
17
|
+
.option('baseUrl', {
|
|
18
|
+
alias: ['base-url', 'url'],
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: process.env.SONAR_BASE_URL ?? 'https://sonarcloud.io',
|
|
21
|
+
defaultDescription: 'SONAR_BASE_URL or https://sonarcloud.io',
|
|
22
|
+
coerce: (value) => String(value).replace(/\/$/, ''),
|
|
23
|
+
})
|
|
24
|
+
.option('comment', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
describe: 'Optional acceptance comment',
|
|
27
|
+
})
|
|
28
|
+
.option('json', {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
default: false,
|
|
31
|
+
describe: 'Print raw JSON for automation/agents',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function handler(args) {
|
|
35
|
+
const result = await transitionIssue({
|
|
36
|
+
token: args.token,
|
|
37
|
+
baseUrl: args.baseUrl,
|
|
38
|
+
}, {
|
|
39
|
+
issueKey: args.issueKey,
|
|
40
|
+
transition: 'accept',
|
|
41
|
+
comment: args.comment,
|
|
42
|
+
});
|
|
43
|
+
if (args.json) {
|
|
44
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(`Accepted issue ${result.issueKey} (${result.transition})\n`);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=issue-accept.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-accept.js","sourceRoot":"","sources":["../../../src/cli/commands/issue-accept.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAA;AAUnE,MAAM,CAAC,MAAM,OAAO,GAAG,yBAAyB,CAAA;AAChD,MAAM,CAAC,MAAM,QAAQ,GAAG,oDAAoD,CAAA;AAE5E,MAAM,UAAU,OAAO,CAAC,KAAW;IACjC,OAAO,KAAK;SACT,UAAU,CAAC,UAAU,EAAE;QACtB,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,sBAAsB;QACpC,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;KACjD,CAAC;SACD,MAAM,CAAC,OAAO,EAAE;QACf,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;QAChC,kBAAkB,EAAE,aAAa;QACjC,YAAY,EAAE,oCAAoC;KACnD,CAAC;SACD,MAAM,CAAC,SAAS,EAAE;QACjB,KAAK,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;QAC1B,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,uBAAuB;QAC9D,kBAAkB,EAAE,yCAAyC;QAC7D,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;KAC7D,CAAC;SACD,MAAM,CAAC,SAAS,EAAE;QACjB,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,6BAA6B;KACxC,CAAC;SACD,MAAM,CAAC,MAAM,EAAE;QACd,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,sCAAsC;KACjD,CAAC,CAAA;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAqB;IACjD,MAAM,MAAM,GAAG,MAAM,eAAe,CAClC;QACE,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,EACD;QACE,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,QAAQ;QACpB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CACF,CAAA;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QAC5D,OAAM;IACR,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,UAAU,KAAK,CAAC,CAAA;AACpF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
type PullRequestCoverageArgs = {
|
|
3
|
+
token: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
projectKey?: string;
|
|
6
|
+
pullRequest: string;
|
|
7
|
+
threshold: number;
|
|
8
|
+
includePassing: boolean;
|
|
9
|
+
maxFiles: number;
|
|
10
|
+
json: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const command = "pr-coverage <pullRequest> [projectKey]";
|
|
13
|
+
export declare const describe = "List files with low new-code coverage for a pull request";
|
|
14
|
+
export declare function builder(yargs: Argv): Argv<PullRequestCoverageArgs>;
|
|
15
|
+
export declare function handler(args: PullRequestCoverageArgs): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { resolveProjectKey } from '../project-key.js';
|
|
2
|
+
import { getPullRequestCoverage } from '../../service/pr-coverage.js';
|
|
3
|
+
export const command = 'pr-coverage <pullRequest> [projectKey]';
|
|
4
|
+
export const describe = 'List files with low new-code coverage for a pull request';
|
|
5
|
+
export function builder(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.positional('projectKey', {
|
|
8
|
+
type: 'string',
|
|
9
|
+
describe: 'Sonar project key (optional; auto-detected from sonar-project.properties)',
|
|
10
|
+
})
|
|
11
|
+
.positional('pullRequest', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
demandOption: 'Provide a pull request key/id',
|
|
14
|
+
coerce: (value) => String(value).trim(),
|
|
15
|
+
})
|
|
16
|
+
.option('token', {
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: process.env.SONAR_TOKEN,
|
|
19
|
+
defaultDescription: 'SONAR_TOKEN',
|
|
20
|
+
demandOption: 'Provide --token or set SONAR_TOKEN',
|
|
21
|
+
})
|
|
22
|
+
.option('baseUrl', {
|
|
23
|
+
alias: ['base-url', 'url'],
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: process.env.SONAR_BASE_URL ?? 'https://sonarcloud.io',
|
|
26
|
+
defaultDescription: 'SONAR_BASE_URL or https://sonarcloud.io',
|
|
27
|
+
coerce: (value) => String(value).replace(/\/$/, ''),
|
|
28
|
+
})
|
|
29
|
+
.option('projectKey', {
|
|
30
|
+
alias: ['project-key', 'k'],
|
|
31
|
+
type: 'string',
|
|
32
|
+
describe: 'Sonar project key (overrides auto-detection)',
|
|
33
|
+
})
|
|
34
|
+
.option('threshold', {
|
|
35
|
+
type: 'number',
|
|
36
|
+
default: 80,
|
|
37
|
+
describe: 'Coverage threshold percentage',
|
|
38
|
+
coerce: (value) => Math.max(0, Math.min(100, Number(value))),
|
|
39
|
+
})
|
|
40
|
+
.option('includePassing', {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
default: false,
|
|
43
|
+
describe: 'Include files that meet threshold too',
|
|
44
|
+
})
|
|
45
|
+
.option('maxFiles', {
|
|
46
|
+
alias: ['max', 'limit'],
|
|
47
|
+
type: 'number',
|
|
48
|
+
default: 20,
|
|
49
|
+
coerce: (value) => Math.max(1, Number(value)),
|
|
50
|
+
})
|
|
51
|
+
.option('json', {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
default: false,
|
|
54
|
+
describe: 'Print raw JSON for automation/agents',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export async function handler(args) {
|
|
58
|
+
const projectKey = resolveProjectKey(args.projectKey);
|
|
59
|
+
const report = await getPullRequestCoverage({
|
|
60
|
+
token: args.token,
|
|
61
|
+
baseUrl: args.baseUrl,
|
|
62
|
+
}, {
|
|
63
|
+
projectKey,
|
|
64
|
+
pullRequest: args.pullRequest,
|
|
65
|
+
threshold: args.threshold,
|
|
66
|
+
includePassing: args.includePassing,
|
|
67
|
+
maxFiles: args.maxFiles,
|
|
68
|
+
});
|
|
69
|
+
if (args.json) {
|
|
70
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write(`Found ${report.files.length} file(s) below ${report.threshold.toFixed(1)}% new-code coverage\n`);
|
|
74
|
+
for (const file of report.files) {
|
|
75
|
+
process.stdout.write(`- ${file.file}: ${file.coverageOnNewCode.toFixed(1)}% (${file.uncoveredLines}/${file.linesToCover} uncovered lines)\n`);
|
|
76
|
+
}
|
|
77
|
+
process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=pr-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-coverage.js","sourceRoot":"","sources":["../../../src/cli/commands/pr-coverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
type PullRequestIssuesArgs = {
|
|
3
|
+
token: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
projectKey?: string;
|
|
6
|
+
pullRequest: string;
|
|
7
|
+
page: number;
|
|
8
|
+
pageSize: number;
|
|
9
|
+
json: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare const command = "pr-issues <pullRequest> [projectKey]";
|
|
12
|
+
export declare const describe = "Fetch SonarQube Cloud issue details for a pull request";
|
|
13
|
+
export declare function builder(yargs: Argv): Argv<PullRequestIssuesArgs>;
|
|
14
|
+
export declare function handler(args: PullRequestIssuesArgs): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { resolveProjectKey } from '../project-key.js';
|
|
2
|
+
import { getPullRequestIssues } from '../../service/pr-issues.js';
|
|
3
|
+
export const command = 'pr-issues <pullRequest> [projectKey]';
|
|
4
|
+
export const describe = 'Fetch SonarQube Cloud issue details for a pull request';
|
|
5
|
+
export function builder(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.positional('projectKey', {
|
|
8
|
+
type: 'string',
|
|
9
|
+
describe: 'Sonar project key (optional; auto-detected from sonar-project.properties)',
|
|
10
|
+
})
|
|
11
|
+
.positional('pullRequest', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
demandOption: 'Provide a pull request key/id',
|
|
14
|
+
coerce: (value) => String(value).trim(),
|
|
15
|
+
})
|
|
16
|
+
.option('token', {
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: process.env.SONAR_TOKEN,
|
|
19
|
+
defaultDescription: 'SONAR_TOKEN',
|
|
20
|
+
demandOption: 'Provide --token or set SONAR_TOKEN',
|
|
21
|
+
})
|
|
22
|
+
.option('baseUrl', {
|
|
23
|
+
alias: ['base-url', 'url'],
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: process.env.SONAR_BASE_URL ?? 'https://sonarcloud.io',
|
|
26
|
+
defaultDescription: 'SONAR_BASE_URL or https://sonarcloud.io',
|
|
27
|
+
coerce: (value) => String(value).replace(/\/$/, ''),
|
|
28
|
+
})
|
|
29
|
+
.option('projectKey', {
|
|
30
|
+
alias: ['project-key', 'k'],
|
|
31
|
+
type: 'string',
|
|
32
|
+
describe: 'Sonar project key (overrides auto-detection)',
|
|
33
|
+
})
|
|
34
|
+
.option('page', {
|
|
35
|
+
alias: 'p',
|
|
36
|
+
type: 'number',
|
|
37
|
+
default: 1,
|
|
38
|
+
coerce: (value) => Math.max(1, Number(value)),
|
|
39
|
+
})
|
|
40
|
+
.option('pageSize', {
|
|
41
|
+
alias: ['ps', 'page-size'],
|
|
42
|
+
type: 'number',
|
|
43
|
+
default: 100,
|
|
44
|
+
coerce: (value) => Math.min(500, Math.max(1, Number(value))),
|
|
45
|
+
})
|
|
46
|
+
.option('json', {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default: false,
|
|
49
|
+
describe: 'Print raw JSON for automation/agents',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function handler(args) {
|
|
53
|
+
const projectKey = resolveProjectKey(args.projectKey);
|
|
54
|
+
const report = await getPullRequestIssues({
|
|
55
|
+
token: args.token,
|
|
56
|
+
baseUrl: args.baseUrl,
|
|
57
|
+
}, {
|
|
58
|
+
projectKey,
|
|
59
|
+
pullRequest: args.pullRequest,
|
|
60
|
+
page: args.page,
|
|
61
|
+
pageSize: args.pageSize,
|
|
62
|
+
});
|
|
63
|
+
if (args.json) {
|
|
64
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`Found ${report.total} open issue(s) in new code for PR ${report.pullRequest} (${report.projectKey})\n`);
|
|
68
|
+
if (report.issues.length === 0) {
|
|
69
|
+
process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const issue of report.issues) {
|
|
73
|
+
const location = issue.line ? `${issue.file}:${issue.line}` : issue.file;
|
|
74
|
+
process.stdout.write(`- [${issue.issueStatus}] ${issue.severity} ${issue.type} ${issue.rule} ${location} - ${issue.message}\n`);
|
|
75
|
+
}
|
|
76
|
+
process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=pr-issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-issues.js","sourceRoot":"","sources":["../../../src/cli/commands/pr-issues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
type PullRequestReportArgs = {
|
|
3
|
+
token: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
projectKey?: string;
|
|
6
|
+
pullRequest: string;
|
|
7
|
+
json: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const command = "pr-report <pullRequest> [projectKey]";
|
|
10
|
+
export declare const describe = "Fetch SonarQube Cloud details for a pull request";
|
|
11
|
+
export declare function builder(yargs: Argv): Argv<PullRequestReportArgs>;
|
|
12
|
+
export declare function handler(args: PullRequestReportArgs): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { resolveProjectKey } from '../project-key.js';
|
|
2
|
+
import { getPullRequestReport } from '../../service/pr-report.js';
|
|
3
|
+
export const command = 'pr-report <pullRequest> [projectKey]';
|
|
4
|
+
export const describe = 'Fetch SonarQube Cloud details for a pull request';
|
|
5
|
+
export function builder(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.positional('projectKey', {
|
|
8
|
+
type: 'string',
|
|
9
|
+
describe: 'Sonar project key (optional; auto-detected from sonar-project.properties)',
|
|
10
|
+
})
|
|
11
|
+
.positional('pullRequest', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
demandOption: 'Provide a pull request key/id',
|
|
14
|
+
coerce: (value) => String(value).trim(),
|
|
15
|
+
})
|
|
16
|
+
.option('token', {
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: process.env.SONAR_TOKEN,
|
|
19
|
+
defaultDescription: 'SONAR_TOKEN',
|
|
20
|
+
demandOption: 'Provide --token or set SONAR_TOKEN',
|
|
21
|
+
})
|
|
22
|
+
.option('baseUrl', {
|
|
23
|
+
alias: ['base-url', 'url'],
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: process.env.SONAR_BASE_URL ?? 'https://sonarcloud.io',
|
|
26
|
+
defaultDescription: 'SONAR_BASE_URL or https://sonarcloud.io',
|
|
27
|
+
coerce: (value) => String(value).replace(/\/$/, ''),
|
|
28
|
+
})
|
|
29
|
+
.option('projectKey', {
|
|
30
|
+
alias: ['project-key', 'k'],
|
|
31
|
+
type: 'string',
|
|
32
|
+
describe: 'Sonar project key (overrides auto-detection)',
|
|
33
|
+
})
|
|
34
|
+
.option('json', {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
default: false,
|
|
37
|
+
describe: 'Print raw JSON for automation/agents',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function handler(args) {
|
|
41
|
+
const projectKey = resolveProjectKey(args.projectKey);
|
|
42
|
+
const report = await getPullRequestReport({
|
|
43
|
+
token: args.token,
|
|
44
|
+
baseUrl: args.baseUrl,
|
|
45
|
+
}, {
|
|
46
|
+
projectKey,
|
|
47
|
+
pullRequest: args.pullRequest,
|
|
48
|
+
});
|
|
49
|
+
if (args.json) {
|
|
50
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const qualityGate = report.qualityGateStatus === 'OK'
|
|
54
|
+
? 'Quality Gate passed'
|
|
55
|
+
: `Quality Gate failed (${report.qualityGateStatus})`;
|
|
56
|
+
process.stdout.write(`${qualityGate}\n\n`);
|
|
57
|
+
if (report.failingQualityGateConditions.length > 0) {
|
|
58
|
+
process.stdout.write('Quality Gate Conditions\n');
|
|
59
|
+
for (const condition of report.failingQualityGateConditions) {
|
|
60
|
+
const metric = formatMetricLabel(condition.metricKey);
|
|
61
|
+
const threshold = condition.errorThreshold ?? 'n/a';
|
|
62
|
+
const actual = condition.actualValue ?? 'n/a';
|
|
63
|
+
process.stdout.write(`- ${metric}: actual=${actual}, threshold=${condition.comparator} ${threshold}\n`);
|
|
64
|
+
}
|
|
65
|
+
process.stdout.write('\n');
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write('Issues\n');
|
|
68
|
+
process.stdout.write(`- ${report.issueCounts.newIssues} New issues\n`);
|
|
69
|
+
process.stdout.write(`- ${report.issueCounts.acceptedIssues} Accepted issues\n\n`);
|
|
70
|
+
process.stdout.write('Measures\n');
|
|
71
|
+
process.stdout.write(`- ${report.measures.securityHotspots} Security Hotspots\n`);
|
|
72
|
+
process.stdout.write(`- ${report.measures.coverageOnNewCode.toFixed(1)}% Coverage on New Code\n`);
|
|
73
|
+
process.stdout.write(`- ${report.measures.duplicationOnNewCode.toFixed(1)}% Duplication on New Code\n\n`);
|
|
74
|
+
process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
|
|
75
|
+
}
|
|
76
|
+
function formatMetricLabel(metricKey) {
|
|
77
|
+
switch (metricKey) {
|
|
78
|
+
case 'new_coverage':
|
|
79
|
+
return 'Coverage on New Code';
|
|
80
|
+
case 'new_duplicated_lines_density':
|
|
81
|
+
return 'Duplication on New Code';
|
|
82
|
+
case 'new_security_hotspots_reviewed':
|
|
83
|
+
return 'Security Hotspots Reviewed on New Code';
|
|
84
|
+
case 'new_reliability_rating':
|
|
85
|
+
return 'Reliability Rating on New Code';
|
|
86
|
+
case 'new_security_rating':
|
|
87
|
+
return 'Security Rating on New Code';
|
|
88
|
+
case 'new_maintainability_rating':
|
|
89
|
+
return 'Maintainability Rating on New Code';
|
|
90
|
+
default:
|
|
91
|
+
return metricKey;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=pr-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-report.js","sourceRoot":"","sources":["../../../src/cli/commands/pr-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
type PullRequestReviewArgs = {
|
|
3
|
+
token: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
projectKey?: string;
|
|
6
|
+
pullRequest: string;
|
|
7
|
+
contextLines: number;
|
|
8
|
+
page: number;
|
|
9
|
+
pageSize: number;
|
|
10
|
+
json: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const command = "pr-review <pullRequest> [projectKey]";
|
|
13
|
+
export declare const describe = "Review Sonar pull request issues with code context snippets";
|
|
14
|
+
export declare function builder(yargs: Argv): Argv<PullRequestReviewArgs>;
|
|
15
|
+
export declare function handler(args: PullRequestReviewArgs): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { resolveProjectKey } from '../project-key.js';
|
|
2
|
+
import { getPullRequestReview } from '../../service/pr-review.js';
|
|
3
|
+
export const command = 'pr-review <pullRequest> [projectKey]';
|
|
4
|
+
export const describe = 'Review Sonar pull request issues with code context snippets';
|
|
5
|
+
export function builder(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.positional('projectKey', {
|
|
8
|
+
type: 'string',
|
|
9
|
+
describe: 'Sonar project key (optional; auto-detected from sonar-project.properties)',
|
|
10
|
+
})
|
|
11
|
+
.positional('pullRequest', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
demandOption: 'Provide a pull request key/id',
|
|
14
|
+
coerce: (value) => String(value).trim(),
|
|
15
|
+
})
|
|
16
|
+
.option('token', {
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: process.env.SONAR_TOKEN,
|
|
19
|
+
defaultDescription: 'SONAR_TOKEN',
|
|
20
|
+
demandOption: 'Provide --token or set SONAR_TOKEN',
|
|
21
|
+
})
|
|
22
|
+
.option('baseUrl', {
|
|
23
|
+
alias: ['base-url', 'url'],
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: process.env.SONAR_BASE_URL ?? 'https://sonarcloud.io',
|
|
26
|
+
defaultDescription: 'SONAR_BASE_URL or https://sonarcloud.io',
|
|
27
|
+
coerce: (value) => String(value).replace(/\/$/, ''),
|
|
28
|
+
})
|
|
29
|
+
.option('projectKey', {
|
|
30
|
+
alias: ['project-key', 'k'],
|
|
31
|
+
type: 'string',
|
|
32
|
+
describe: 'Sonar project key (overrides auto-detection)',
|
|
33
|
+
})
|
|
34
|
+
.option('contextLines', {
|
|
35
|
+
alias: ['context', 'C'],
|
|
36
|
+
type: 'number',
|
|
37
|
+
default: 3,
|
|
38
|
+
coerce: (value) => Math.max(0, Number(value)),
|
|
39
|
+
})
|
|
40
|
+
.option('page', {
|
|
41
|
+
alias: 'p',
|
|
42
|
+
type: 'number',
|
|
43
|
+
default: 1,
|
|
44
|
+
coerce: (value) => Math.max(1, Number(value)),
|
|
45
|
+
})
|
|
46
|
+
.option('pageSize', {
|
|
47
|
+
alias: ['ps', 'page-size'],
|
|
48
|
+
type: 'number',
|
|
49
|
+
default: 20,
|
|
50
|
+
coerce: (value) => Math.min(500, Math.max(1, Number(value))),
|
|
51
|
+
})
|
|
52
|
+
.option('json', {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
default: false,
|
|
55
|
+
describe: 'Print raw JSON for automation/agents',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function handler(args) {
|
|
59
|
+
const projectKey = resolveProjectKey(args.projectKey);
|
|
60
|
+
const report = await getPullRequestReview({
|
|
61
|
+
token: args.token,
|
|
62
|
+
baseUrl: args.baseUrl,
|
|
63
|
+
}, {
|
|
64
|
+
projectKey,
|
|
65
|
+
pullRequest: args.pullRequest,
|
|
66
|
+
contextLines: args.contextLines,
|
|
67
|
+
page: args.page,
|
|
68
|
+
pageSize: args.pageSize,
|
|
69
|
+
});
|
|
70
|
+
if (args.json) {
|
|
71
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write(`Found ${report.total} open issue(s) in new code for PR ${report.pullRequest} (${report.projectKey})\n\n`);
|
|
75
|
+
for (const issue of report.issues) {
|
|
76
|
+
const location = issue.line ? `${issue.file}:${issue.line}` : issue.file;
|
|
77
|
+
process.stdout.write(`[${issue.issueStatus}] ${issue.severity} ${issue.type} ${issue.rule} ${location}\n${issue.message}\n`);
|
|
78
|
+
process.stdout.write(`Issue URL: ${issue.issueUrl}\n`);
|
|
79
|
+
if (issue.snippet) {
|
|
80
|
+
process.stdout.write('Context:\n');
|
|
81
|
+
for (const line of issue.snippet.lines) {
|
|
82
|
+
const marker = line.highlight ? '>' : ' ';
|
|
83
|
+
process.stdout.write(`${marker} ${line.line.toString().padStart(4, ' ')} | ${line.text}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (issue.sourceError) {
|
|
87
|
+
process.stdout.write(`Context unavailable: ${issue.sourceError}\n`);
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write('\n');
|
|
90
|
+
}
|
|
91
|
+
process.stdout.write(`See analysis details: ${report.analysisUrl}\n`);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=pr-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-review.js","sourceRoot":"","sources":["../../../src/cli/commands/pr-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv?: string[]): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import yargs from 'yargs/yargs';
|
|
4
|
+
export async function run(argv = process.argv) {
|
|
5
|
+
const commandsDir = fileURLToPath(new URL('./commands', import.meta.url));
|
|
6
|
+
const cli = yargs(hideBin(argv))
|
|
7
|
+
.scriptName('sonar-sweep')
|
|
8
|
+
.env('SONAR')
|
|
9
|
+
.commandDir(commandsDir, {
|
|
10
|
+
extensions: ['js'],
|
|
11
|
+
exclude: /\.test\.(ts|js)$/,
|
|
12
|
+
});
|
|
13
|
+
await cli
|
|
14
|
+
.demandCommand(1, 'Provide a command')
|
|
15
|
+
.strict()
|
|
16
|
+
.help()
|
|
17
|
+
.fail((message, error, yargsInstance) => {
|
|
18
|
+
if (error) {
|
|
19
|
+
process.stderr.write(`${error.message}\n`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
process.stderr.write(`${message}\n`);
|
|
23
|
+
yargsInstance.showHelp();
|
|
24
|
+
process.exit(1);
|
|
25
|
+
})
|
|
26
|
+
.parseAsync();
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,aAAa,CAAA;AAE/B,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAiB,OAAO,CAAC,IAAI;IACrD,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAEzE,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC7B,UAAU,CAAC,aAAa,CAAC;SACzB,GAAG,CAAC,OAAO,CAAC;SACZ,UAAU,CAAC,WAAW,EAAE;QACvB,UAAU,EAAE,CAAC,IAAI,CAAC;QAClB,OAAO,EAAE,kBAAkB;KAC5B,CAAC,CAAA;IAEJ,MAAM,GAAG;SACN,aAAa,CAAC,CAAC,EAAE,mBAAmB,CAAC;SACrC,MAAM,EAAE;SACR,IAAI,EAAE;SACN,IAAI,CAAC,CAAC,OAAe,EAAE,KAAwB,EAAE,aAAa,EAAE,EAAE;QACjE,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;YAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAA;QACpC,aAAa,CAAC,QAAQ,EAAE,CAAA;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC;SACD,UAAU,EAAE,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveProjectKey(projectKey?: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export function resolveProjectKey(projectKey) {
|
|
5
|
+
if (projectKey?.trim()) {
|
|
6
|
+
return projectKey.trim();
|
|
7
|
+
}
|
|
8
|
+
const gitRoot = getGitRoot();
|
|
9
|
+
const sonarPropsPath = join(gitRoot, 'sonar-project.properties');
|
|
10
|
+
if (!existsSync(sonarPropsPath)) {
|
|
11
|
+
throw new Error(`Missing projectKey and no sonar-project.properties found at ${sonarPropsPath}. Provide --projectKey explicitly.`);
|
|
12
|
+
}
|
|
13
|
+
const content = readFileSync(sonarPropsPath, 'utf8');
|
|
14
|
+
const match = content.match(/^\s*sonar\.projectKey\s*=\s*(.+)\s*$/m);
|
|
15
|
+
const key = match?.[1]?.trim();
|
|
16
|
+
if (!key) {
|
|
17
|
+
throw new Error(`Could not read sonar.projectKey from ${sonarPropsPath}. Provide --projectKey explicitly.`);
|
|
18
|
+
}
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
function getGitRoot() {
|
|
22
|
+
try {
|
|
23
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
26
|
+
}).trim();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return process.cwd();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=project-key.js.map
|