Skip to content

Commit 1542544

Browse files
CLI-77 Auto-install sonar-secrets during sonar integrate claude
1 parent d272df0 commit 1542544

File tree

6 files changed

+77
-153
lines changed

6 files changed

+77
-153
lines changed

README.md

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -119,40 +119,6 @@ sonar auth status
119119

120120
---
121121

122-
### `sonar install`
123-
124-
Install Sonar tools
125-
126-
#### `sonar install secrets`
127-
128-
Install sonar-secrets binary from https://binaries.sonarsource.com
129-
130-
**Options:**
131-
132-
| Option | Type | Required | Description | Default |
133-
| ---------- | ------- | -------- | ----------------------------------------------- | ------- |
134-
| `--force` | boolean | No | Force reinstall even if already installed | - |
135-
| `--status` | boolean | No | Check installation status instead of installing | - |
136-
137-
**Examples:**
138-
139-
Install latest sonar-secrets binary
140-
```bash
141-
sonar install secrets
142-
```
143-
144-
Reinstall sonar-secrets (overwrite existing)
145-
```bash
146-
sonar install secrets --force
147-
```
148-
149-
Check if sonar-secrets is installed and up to date
150-
```bash
151-
sonar install secrets --status
152-
```
153-
154-
---
155-
156122
### `sonar integrate`
157123

158124
Setup SonarQube integration for AI coding agents, git and others.

src/cli/command-tree.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { authLogin, type AuthLoginOptions } from './commands/auth/login';
2828
import { authLogout, type AuthLogoutOptions } from './commands/auth/logout';
2929
import { authPurge } from './commands/auth/purge';
3030
import { authStatus } from './commands/auth/status';
31-
import { installSecrets, type InstallSecretsOptions } from './commands/install/secrets';
3231
import { integrateClaude, type IntegrateClaudeOptions } from './commands/integrate/claude';
3332
import { analyzeSecrets, type AnalyzeSecretsOptions } from './commands/analyze/secrets';
3433
import { analyzeSqaa, type AnalyzeSqaaOptions } from './commands/analyze/sqaa';
@@ -65,16 +64,6 @@ COMMAND_TREE.name('sonar')
6564
.addHelpText('beforeAll', getHelpBanner())
6665
.enablePositionalOptions();
6766

68-
// Install Sonar tools
69-
const install = COMMAND_TREE.command('install').description('Install Sonar tools');
70-
71-
install
72-
.command('secrets')
73-
.description('Install sonar-secrets binary from https://binaries.sonarsource.com')
74-
.option('--force', 'Force reinstall even if already installed')
75-
.option('--status', 'Check installation status instead of installing')
76-
.action((options: InstallSecretsOptions) => runCommand(() => installSecrets(options)));
77-
7867
// Setup SonarQube integration for AI coding agent
7968
const integrateCommand = COMMAND_TREE.command('integrate').description(
8069
'Setup SonarQube integration for AI coding agents, git and others.',

src/cli/commands/integrate/claude/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { blank, info, intro, note, outro, success, text, warn } from '../../../.
2929
import type { DiscoveredProject } from '../../_common/discovery';
3030
import { discoverProject } from '../../_common/discovery';
3131
import { CommandFailedError } from '../../_common/error';
32+
import { performSecretInstall } from '../../install/secrets';
3233
import { runHealthChecks } from './health';
3334
import { installHooks } from './hooks';
3435
import { repairToken } from './repair';
@@ -74,6 +75,10 @@ export async function integrateClaude(options: IntegrateClaudeOptions): Promise<
7475
text('Phase 2/3: Health Check & Repair');
7576
blank();
7677

78+
text('Installing sonar-secrets...');
79+
await performSecretInstall({ force: false });
80+
blank();
81+
7782
const healthResult = await runHealthChecks(
7883
config.serverURL,
7984
token || 'INVALID',

tests/integration/specs/install/install-secrets.test.ts

Lines changed: 0 additions & 108 deletions
This file was deleted.

tests/integration/specs/integrate/integrate.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('integrate claude', () => {
3232

3333
beforeEach(async () => {
3434
harness = await TestHarness.create();
35+
harness.state().withSecretsBinaryInstalled();
3536
});
3637

3738
afterEach(async () => {
@@ -436,6 +437,7 @@ describe('integrate claude — A3S entitlement guard', () => {
436437

437438
beforeEach(async () => {
438439
harness = await TestHarness.create();
440+
harness.state().withSecretsBinaryInstalled();
439441
});
440442

441443
afterEach(async () => {
@@ -552,6 +554,7 @@ describe('integrate claude — file placement (local vs global)', () => {
552554

553555
beforeEach(async () => {
554556
harness = await TestHarness.create();
557+
harness.state().withSecretsBinaryInstalled();
555558
});
556559

557560
afterEach(async () => {
@@ -848,6 +851,7 @@ describe('integrate claude — legacy state without agentExtensions', () => {
848851

849852
beforeEach(async () => {
850853
harness = await TestHarness.create();
854+
harness.state().withSecretsBinaryInstalled();
851855
});
852856

853857
afterEach(async () => {
@@ -1092,3 +1096,62 @@ describe('integrate — argument validation', () => {
10921096
{ timeout: 15000 },
10931097
);
10941098
});
1099+
1100+
// ─── sonar-secrets auto-install ───────────────────────────────────────────────
1101+
1102+
describe('integrate claude — sonar-secrets auto-install', () => {
1103+
let harness: TestHarness;
1104+
1105+
beforeEach(async () => {
1106+
harness = await TestHarness.create();
1107+
});
1108+
1109+
afterEach(async () => {
1110+
await harness.dispose();
1111+
});
1112+
1113+
it(
1114+
'downloads and installs sonar-secrets when binary is not present',
1115+
async () => {
1116+
const server = await harness
1117+
.newFakeServer()
1118+
.withAuthToken('test-token')
1119+
.withProject('my-project')
1120+
.start();
1121+
await harness.newFakeBinariesServer().start();
1122+
harness.cwd.writeFile(
1123+
'sonar-project.properties',
1124+
[`sonar.host.url=${server.baseUrl()}`, 'sonar.projectKey=my-project'].join('\n'),
1125+
);
1126+
1127+
const result = await harness.run('integrate claude --token test-token --non-interactive');
1128+
1129+
expect(result.exitCode).toBe(0);
1130+
expect(harness.cliHome.file('bin', 'sonar-secrets').exists()).toBe(true);
1131+
},
1132+
{ timeout: 30000 },
1133+
);
1134+
1135+
it(
1136+
'skips download when sonar-secrets is already installed at the correct version',
1137+
async () => {
1138+
const server = await harness
1139+
.newFakeServer()
1140+
.withAuthToken('test-token')
1141+
.withProject('my-project')
1142+
.start();
1143+
const fakeBinariesServer = await harness.newFakeBinariesServer().start();
1144+
harness.state().withSecretsBinaryInstalled();
1145+
harness.cwd.writeFile(
1146+
'sonar-project.properties',
1147+
[`sonar.host.url=${server.baseUrl()}`, 'sonar.projectKey=my-project'].join('\n'),
1148+
);
1149+
1150+
const result = await harness.run('integrate claude --token test-token --non-interactive');
1151+
1152+
expect(result.exitCode).toBe(0);
1153+
expect(fakeBinariesServer.getRecordedRequests()).toHaveLength(0);
1154+
},
1155+
{ timeout: 30000 },
1156+
);
1157+
});

tests/unit/integrate.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import * as repair from '../../src/cli/commands/integrate/claude/repair';
3131
import * as state from '../../src/cli/commands/integrate/claude/state';
3232
import * as authResolver from '../../src/lib/auth-resolver';
3333
import { ResolvedAuth } from '../../src/lib/auth-resolver';
34+
import * as installSecrets from '../../src/cli/commands/install/secrets';
3435
import * as migration from '../../src/lib/migration';
3536
import { getDefaultState } from '../../src/lib/state';
3637
import * as stateManager from '../../src/lib/state-manager';
@@ -80,6 +81,9 @@ describe('integrateCommand', () => {
8081
let updateStateAfterConfigurationSpy: Mock<
8182
Extract<(typeof state)['updateStateAfterConfiguration'], (...args: any[]) => any>
8283
>;
84+
let performSecretInstallSpy: Mock<
85+
Extract<(typeof installSecrets)['performSecretInstall'], (...args: any[]) => any>
86+
>;
8387

8488
beforeEach(() => {
8589
setMockUi(true);
@@ -99,6 +103,10 @@ describe('integrateCommand', () => {
99103
runMigrationsSpy = spyOn(migration, 'runMigrations');
100104
updateStateAfterConfigurationSpy = spyOn(state, 'updateStateAfterConfiguration');
101105

106+
performSecretInstallSpy = spyOn(installSecrets, 'performSecretInstall').mockResolvedValue(
107+
'/fake/path/sonar-secrets',
108+
);
109+
102110
mockDiscoveredProject({}); // Default mock to prevent tests from reading the real filesystem. Individual tests are overriding this with specific project data as needed.
103111
mockHealthCheck(); // Default mock to healthy checks. Individual tests are overriding this with specific health data as needed.
104112
});
@@ -117,6 +125,7 @@ describe('integrateCommand', () => {
117125
installHooksSpy.mockRestore();
118126
runMigrationsSpy.mockRestore();
119127
updateStateAfterConfigurationSpy.mockRestore();
128+
performSecretInstallSpy.mockRestore();
120129
});
121130

122131
it('shows intro message', async () => {

0 commit comments

Comments
 (0)