Skip to content

Commit 2b826b0

Browse files
CLI-41: Support secrets integration without SonarQube authentication
When no SonarQube server is configured, sonar integrate now installs secret scanning hooks without requiring a full SonarQube setup (secrets-only mode). This allows users to get secret scanning protection immediately, without needing a SonarQube account. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4bfd741 commit 2b826b0

File tree

2 files changed

+38
-10
lines changed

2 files changed

+38
-10
lines changed

src/commands/integrate.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,19 @@ export async function integrateCommand(agent: string, options: OnboardAgentOptio
399399
// Load configuration from all sources
400400
const config = await loadConfiguration(projectInfo, options);
401401

402-
// Validate and extract required values
402+
// Secrets-only mode: no project key configured — install hooks and exit
403+
if (!config.projectKey) {
404+
text('\nNo project key configured.');
405+
text('Installing secret scanning hooks only.');
406+
if (!options.skipHooks) {
407+
await installSecretScanningHooks(projectInfo.root);
408+
await updateStateAfterConfiguration(true);
409+
}
410+
outro('Setup complete!', 'success');
411+
return;
412+
}
413+
414+
// Full SonarQube integration path
403415
const { serverURL, projectKey } = validateAndPrintConfiguration(config);
404416

405417
// Ensure token is available

tests/unit/integrate.test.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import * as health from '../../src/bootstrap/health.js';
2727
import * as repair from '../../src/bootstrap/repair.js';
2828
import * as auth from '../../src/bootstrap/auth.js';
2929
import * as keychain from '../../src/lib/keychain.js';
30+
import * as hooks from '../../src/bootstrap/hooks.js';
3031
import * as stateManager from '../../src/lib/state-manager.js';
3132
import { getDefaultState } from '../../src/lib/state.js';
3233
import { setMockUi, getMockUiCalls, clearMockUiCalls } from '../../src/ui';
@@ -318,27 +319,42 @@ describe('integrateCommand: configuration validation', () => {
318319
setMockUi(false);
319320
});
320321

321-
it('exits 1 when server URL cannot be determined', async () => {
322+
it('exits 0 and installs hooks when no project key is configured (secrets-only mode)', async () => {
322323
const discoverSpy = spyOn(discovery, 'discoverProject').mockResolvedValue(FAKE_PROJECT_INFO);
324+
const hooksSpy = spyOn(hooks, 'installSecretScanningHooks').mockResolvedValue(undefined);
323325
try {
324-
await integrateCommand('claude', { project: 'my-project' });
325-
expect(mockExit).toHaveBeenCalledWith(1);
326-
const errors = getMockUiCalls().filter(c => c.method === 'error').map(c => String(c.args[0]));
327-
expect(errors.some(m => m.includes('Server URL'))).toBe(true);
326+
await integrateCommand('claude', {});
327+
expect(mockExit).toHaveBeenCalledWith(0);
328+
expect(hooksSpy).toHaveBeenCalled();
329+
} finally {
330+
discoverSpy.mockRestore();
331+
hooksSpy.mockRestore();
332+
}
333+
});
334+
335+
it('shows secrets-only message when no project key is configured', async () => {
336+
const discoverSpy = spyOn(discovery, 'discoverProject').mockResolvedValue(FAKE_PROJECT_INFO);
337+
const hooksSpy = spyOn(hooks, 'installSecretScanningHooks').mockResolvedValue(undefined);
338+
try {
339+
await integrateCommand('claude', {});
340+
const texts = getMockUiCalls().filter(c => c.method === 'text').map(c => String(c.args[0]));
341+
expect(texts.some(m => m.includes('No project key'))).toBe(true);
328342
} finally {
329343
discoverSpy.mockRestore();
344+
hooksSpy.mockRestore();
330345
}
331346
});
332347

333-
it('exits 1 when project key cannot be determined', async () => {
348+
it('exits 0 and installs hooks when server is set but no project key (secrets-only mode)', async () => {
334349
const discoverSpy = spyOn(discovery, 'discoverProject').mockResolvedValue(FAKE_PROJECT_INFO);
350+
const hooksSpy = spyOn(hooks, 'installSecretScanningHooks').mockResolvedValue(undefined);
335351
try {
336352
await integrateCommand('claude', { server: 'https://sonarcloud.io' });
337-
expect(mockExit).toHaveBeenCalledWith(1);
338-
const errors = getMockUiCalls().filter(c => c.method === 'error').map(c => String(c.args[0]));
339-
expect(errors.some(m => m.includes('Project key'))).toBe(true);
353+
expect(mockExit).toHaveBeenCalledWith(0);
354+
expect(hooksSpy).toHaveBeenCalled();
340355
} finally {
341356
discoverSpy.mockRestore();
357+
hooksSpy.mockRestore();
342358
}
343359
});
344360

0 commit comments

Comments
 (0)