Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 9 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ The authentication step is optional. With authentication, more types of secrets

```
sonar auth login
sonar install secrets
sonar integrate claude -g
```

Expand Down Expand Up @@ -119,40 +118,6 @@ sonar auth status

---

### `sonar install`

Install Sonar tools

#### `sonar install secrets`

Install sonar-secrets binary from https://binaries.sonarsource.com

**Options:**

| Option | Type | Required | Description | Default |
| ---------- | ------- | -------- | ----------------------------------------------- | ------- |
| `--force` | boolean | No | Force reinstall even if already installed | - |
| `--status` | boolean | No | Check installation status instead of installing | - |

**Examples:**

Install latest sonar-secrets binary
```bash
sonar install secrets
```

Reinstall sonar-secrets (overwrite existing)
```bash
sonar install secrets --force
```

Check if sonar-secrets is installed and up to date
```bash
sonar install secrets --status
```

---

### `sonar integrate`

Setup SonarQube integration for AI coding agents, git and others.
Expand All @@ -177,10 +142,7 @@ Setup SonarQube integration for Claude Code. This will install secrets scanning

| Option | Type | Required | Description | Default |
| ------------------- | ------- | -------- | --------------------------------------------------------------------------- | ------- |
| `--server`, `-s` | string | No | SonarQube server URL | - |
| `--project`, `-p` | string | No | Project key | - |
| `--token`, `-t` | string | No | Existing authentication token | - |
| `--org`, `-o` | string | No | Organization key (for SonarQube Cloud) | - |
| `--non-interactive` | boolean | No | Non-interactive mode (no prompts) | - |
| `--global`, `-g` | boolean | No | Install hooks and config globally to ~/.claude instead of project directory | - |

Expand Down Expand Up @@ -233,16 +195,15 @@ Search for issues in SonarQube

**Options:**

| Option | Type | Required | Description | Default |
| ----------------- | ------ | -------- | -------------------------------------- | ------- |
| `--project`, `-p` | string | Yes | Project key | - |
| `--org`, `-o` | string | No | Organization key (for SonarQube Cloud) | - |
| `--severity` | string | No | Filter by severity | - |
| `--format` | string | No | Output format | `json` |
| `--branch` | string | No | Branch name | - |
| `--pull-request` | string | No | Pull request ID | - |
| `--page-size` | number | No | Page size (1-500) | `500` |
| `--page` | number | No | Page number | `1` |
| Option | Type | Required | Description | Default |
| ----------------- | ------ | -------- | ------------------ | ------- |
| `--project`, `-p` | string | Yes | Project key | - |
| `--severity` | string | No | Filter by severity | - |
| `--format` | string | No | Output format | `json` |
| `--branch` | string | No | Branch name | - |
| `--pull-request` | string | No | Pull request ID | - |
| `--page-size` | number | No | Page size (1-500) | `500` |
| `--page` | number | No | Page number | `1` |

**Examples:**

Expand All @@ -266,7 +227,6 @@ Search for projects in SonarQube

| Option | Type | Required | Description | Default |
| --------------- | ------ | -------- | ---------------------------------------------- | ------- |
| `--org`, `-o` | string | No | Organization key (for SonarQube Cloud) | - |
| `--query`, `-q` | string | No | Search query to filter projects by name or key | - |
| `--page` | number | No | Page number | `1` |
| `--page-size` | number | No | Page size (1-500) | `500` |
Expand Down
1 change: 0 additions & 1 deletion build-scripts/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ The authentication step is optional. With authentication, more types of secrets

```
sonar auth login
sonar install secrets
sonar integrate claude -g
```

Expand Down
11 changes: 0 additions & 11 deletions build-scripts/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,6 @@ export const EXAMPLES: Record<string, Example[]> = {
description: 'Show current server connection and token status',
},
],
'sonar install secrets': [
{ command: 'sonar install secrets', description: 'Install latest sonar-secrets binary' },
{
command: 'sonar install secrets --force',
description: 'Reinstall sonar-secrets (overwrite existing)',
},
{
command: 'sonar install secrets --status',
description: 'Check if sonar-secrets is installed and up to date',
},
],
'sonar integrate': [
{
command: 'sonar integrate claude -s https://sonarcloud.io -p my-project',
Expand Down
11 changes: 0 additions & 11 deletions src/cli/command-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { authLogin, type AuthLoginOptions } from './commands/auth/login';
import { authLogout, type AuthLogoutOptions } from './commands/auth/logout';
import { authPurge } from './commands/auth/purge';
import { authStatus } from './commands/auth/status';
import { installSecrets, type InstallSecretsOptions } from './commands/install/secrets';
import { integrateClaude, type IntegrateClaudeOptions } from './commands/integrate/claude';
import { integrateGit, type IntegrateGitOptions } from './commands/integrate/git/index';
import { analyzeSecrets, type AnalyzeSecretsOptions } from './commands/analyze/secrets';
Expand Down Expand Up @@ -66,16 +65,6 @@ COMMAND_TREE.name('sonar')
.addHelpText('beforeAll', getHelpBanner())
.enablePositionalOptions();

// Install Sonar tools
const install = COMMAND_TREE.command('install').description('Install Sonar tools');

install
.command('secrets')
.description('Install sonar-secrets binary from https://binaries.sonarsource.com')
.option('--force', 'Force reinstall even if already installed')
.option('--status', 'Check installation status instead of installing')
.authenticatedAction((_auth, options: InstallSecretsOptions) => installSecrets(options));

// Setup SonarQube integration for AI coding agent
const integrateCommand = COMMAND_TREE.command('integrate').description(
'Setup SonarQube integration for AI coding agents, git and others.',
Expand Down
9 changes: 3 additions & 6 deletions src/cli/commands/analyze/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ async function performPathsScan(

function validateCheckCommandEnvironment(binaryPath: string): void {
if (!existsSync(binaryPath)) {
throw new CommandFailedError(
'sonar-secrets is not installed\n Install with: sonar install secrets',
);
throw new CommandFailedError('sonar-secrets is not installed\n Run: sonar integrate claude');
}
}

Expand Down Expand Up @@ -333,10 +331,9 @@ function handleScanError(err: unknown): void {
details =
'\nThe scan took longer than 30 seconds.\nTry scanning a smaller file or check system resources.';
} else if (errorMessage.includes('ENOENT')) {
details =
'\nThe binary file was not found or is not executable.\nReinstall with: sonar install secrets --force';
details = '\nThe binary file was not found or is not executable.\nRun: sonar integrate claude';
} else {
details = '\nCheck installation with: sonar install secrets --status';
details = '\nRun: sonar integrate claude';
}

throw new CommandFailedError(`Error: ${errorMessage}\n${details}\n`);
Expand Down
93 changes: 4 additions & 89 deletions src/cli/commands/install/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

// Install sonar-secrets binary from binaries.sonarsource.com

import { existsSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { spawnProcess } from '../../../lib/process';
Expand All @@ -40,40 +38,19 @@ import { version as VERSION } from '../../../../package.json';
import logger from '../../../lib/logger';
import type { PlatformInfo } from '../../../lib/install-types';
import { SECRETS_BINARY_NAME } from '../../../lib/install-types';
import { text, blank, note, success, warn, withSpinner, print } from '../../../ui';
import { text, warn, withSpinner, print } from '../../../ui';
import { CommandFailedError } from '../_common/error';

const FILE_EXECUTABLE_PERMS = 0o755; // rwxr-xr-x
const VERSION_REGEX_MAX_SEGMENT = 20;

export interface InstallSecretsOptions {
force?: boolean;
status?: boolean;
}

/**
* CLI wrapper with process exit handling
*/
export async function installSecrets(
options: InstallSecretsOptions,
{ binDir }: { binDir?: string } = {},
): Promise<void> {
if (options.status) {
await installSecretsStatus();
} else {
text('\nInstalling sonar-secrets binary\n');
const binaryPath = await performSecretInstall(options, { binDir });
logInstallationSuccess(binaryPath);
}
}

/**
* Core install logic for sonar-secrets binary download and setup
*/
export async function performSecretInstall(
options: { force?: boolean },
{ binDir }: { binDir?: string } = {},
): Promise<string> {
): Promise<{ binaryPath: string; freshlyInstalled: boolean }> {
const platform = detectPlatform();
const resolvedBinDir = ensureBinDirectory(binDir);
const binaryPath = join(resolvedBinDir, buildLocalBinaryName(platform));
Expand All @@ -82,13 +59,12 @@ export async function performSecretInstall(

try {
await performInstallation(options, platform, binaryPath);
text(` sonar-secrets installed at ${binaryPath}`);
return binaryPath;
return { binaryPath, freshlyInstalled: true };
} catch (err) {
const isAlreadyUpToDate =
(err as Error).message === 'Installation skipped - already up to date';
if (isAlreadyUpToDate) {
return binaryPath;
return { binaryPath, freshlyInstalled: false };
}
throw err;
}
Expand Down Expand Up @@ -139,52 +115,6 @@ async function performInstallation(
recordInstallationInState(installedVersion, binaryPath);
}

/**
* Status command: sonar secret status
*/
async function installSecretsStatus(): Promise<void> {
const platform = detectPlatform();
const binaryPath = join(BIN_DIR, buildLocalBinaryName(platform));

text('\nChecking sonar-secrets installation status\n');

if (!existsSync(binaryPath)) {
text('Status: Not installed');
text(' Install with: sonar install secrets');
return;
}

const version = await checkInstalledVersion(binaryPath);

if (version) {
text(`Status: Installed (v${version})`);
text(`Path: ${binaryPath}`);

// Check for updates
try {
const latestVersion = SONAR_SECRETS_VERSION;

if (version === latestVersion) {
blank();
success('Up to date');
} else {
blank();
warn(`Update available: v${latestVersion}`);
text(' Run: sonar secret install');
}
} catch (err) {
logger.debug(`Failed to check for updates: ${(err as Error).message}`);
warn('Could not check for updates (network/API error)');
}

return;
}

throw new CommandFailedError(
`Binary is installed but could not be called.\nPath: ${binaryPath}\n Reinstall with: sonar install secrets --force`,
);
}

function ensureBinDirectory(dir?: string): string {
const binDir = dir ?? BIN_DIR;
if (!existsSync(binDir)) {
Expand Down Expand Up @@ -265,25 +195,10 @@ async function checkExistingInstallation(binaryPath: string): Promise<boolean> {

if (existingVersion === pinnedVersion) {
text(`sonar-secrets ${existingVersion} is already installed (latest)`);
text(' Use --force to reinstall');
return true;
}

warn(`Version mismatch: ${existingVersion} ≠ ${pinnedVersion}`);
text(' Updating...\n');
return false;
}

function logInstallationSuccess(binaryPath: string): void {
blank();
success('Installation complete!');
note([
`Binary path: ${binaryPath}`,
'',
'Manual usage:',
' sonar analyze secrets [path...]',
'',
'Check installation status:',
' sonar install secrets --status',
]);
}
13 changes: 13 additions & 0 deletions src/cli/commands/integrate/claude/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { blank, info, intro, note, outro, success, text, warn } from '../../../.
import type { DiscoveredProject } from '../../_common/discovery';
import { discoverProject } from '../../_common/discovery';
import { CommandFailedError } from '../../_common/error';
import { performSecretInstall } from '../../install/secrets';
import { runHealthChecks } from './health';
import { installHooks } from './hooks';
import { repairToken } from './repair';
Expand Down Expand Up @@ -70,6 +71,18 @@ export async function integrateClaude(

let token = config.token;

try {
const { binaryPath, freshlyInstalled } = await performSecretInstall({ force: false });
if (freshlyInstalled) {
success(`sonar-secrets installed at ${binaryPath}`);
}
} catch (err) {
warn(`sonar-secrets installation failed: ${(err as Error).message}`);
warn(
'Secrets scanning will not be available until installed. Re-run sonar integrate claude to retry.',
);
}

blank();
text('Phase 2/3: Health Check & Repair');
blank();
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/harness/fake-binaries-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

// Lightweight in-process fake binaries server (Bun.serve).
// Simulates binaries.sonarsource.com so that `sonar install secrets` can be exercised
// Simulates binaries.sonarsource.com so that sonar-secrets auto-install can be exercised
// without real network calls. Serves versioned artifacts from tests/integration/resources/
// — downloaded by setup-integration-resources.ts — and returns 404 for unknown paths.

Expand Down
Loading
Loading