From a322e3d6c15dbb86a835651db334a50b4648cc60 Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Tue, 21 Jul 2026 13:42:41 +0200 Subject: [PATCH 1/4] GROW-256 Print onboarding dashboard link after sonar import completes Once repositories are imported, surface the organization's onboarding dashboard link so users can jump straight to it instead of navigating there manually. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 2 +- src/commands/import/index.ts | 27 ++++++++++++++++--- tests/integration/specs/import/import.test.ts | 3 +++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d33358efe..174b8e17f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ The bare `sonar analyze` command accepts `-p, --project ` for the agent `sonar analyze dependency-risks` pre-scans discovered manifest files for secrets (via `sonar-secrets`) before the SCA scan and aborts if any are found. In the `analyze` path the secrets binary is a hard prerequisite (install failure aborts the run); in the git pre-commit hook path the manifest secrets *scan* fails open on a scan error (skips/warns). See `dependency-risk-helpers/manifest-secrets-guard.ts`. This scan-error fail-open is distinct from the git/agent secrets-hook auth+binary gate: an unauthenticated user or a missing `sonar-secrets` binary blocks the commit/push (git hooks throw `MissingDependenciesError` → exit 1). -`sonar import` (hidden while in development) imports a repository from a connected DevOps platform into SonarQube by provisioning a bound project. Accepts `--org ` and `--repo ` (prompted interactively when omitted) plus `--non-interactive`. Implementation lives in `src/commands/import/`. +`sonar import` (hidden while in development) imports a repository from a connected DevOps platform into SonarQube by provisioning a bound project. Accepts `--org ` and `--repo ` (prompted interactively when omitted) plus `--non-interactive`. On success it prints the organization's onboarding dashboard link (`{serverUrl}/organizations/{orgKey}/onboarding-dashboard`) before the final outro. Implementation lives in `src/commands/import/`. Declarative integration registry helpers live in `src/commands/integrate/_common/registry/index.ts`. New integration descriptors should use that public entrypoint for dependency/resource factories, operations, and registry validation. Command handlers should keep command-specific validation, prompts, and target resolution thin, then delegate feature selection, generic install messages, dependency/resource application, and state recording to `src/commands/integrate/_common/installer.ts`. diff --git a/src/commands/import/index.ts b/src/commands/import/index.ts index 34c58452d..c424e1671 100644 --- a/src/commands/import/index.ts +++ b/src/commands/import/index.ts @@ -177,7 +177,17 @@ function reportSkipped(skipped: readonly SkippedRepo[]): void { } } -function reportOutcome(succeeded: number, failed: number, skippedCount: number): void { +/** Builds the SonarQube Cloud onboarding dashboard link for an organization. */ +function buildOnboardingDashboardUrl(serverUrl: string, orgKey: string): string { + return `${serverUrl.replace(/\/$/, '')}/organizations/${orgKey}/onboarding-dashboard`; +} + +function reportOutcome( + succeeded: number, + failed: number, + skippedCount: number, + dashboardUrl: string, +): void { const skippedSuffix = skippedCount > 0 ? ` (${skippedCount} skipped)` : ''; if (failed > 0) { @@ -192,6 +202,7 @@ function reportOutcome(succeeded: number, failed: number, skippedCount: number): } const succeededNoun = succeeded === 1 ? 'repository' : 'repositories'; + info(`Dashboard: ${dashboardUrl}`); outro(`Imported ${succeeded} ${succeededNoun}${skippedSuffix}`, 'success'); } @@ -210,7 +221,12 @@ export async function importHandler(options: ImportOptions, auth: ResolvedAuth): resolution.collection, ); reportSkipped(skipped); - reportOutcome(succeeded, failed, skipped.length); + reportOutcome( + succeeded, + failed, + skipped.length, + buildOnboardingDashboardUrl(auth.serverUrl, resolution.orgKey), + ); return; } @@ -231,5 +247,10 @@ export async function importHandler(options: ImportOptions, auth: ResolvedAuth): ); const { succeeded, failed } = progress.finish(); - reportOutcome(succeeded, failed, skipped.length); + reportOutcome( + succeeded, + failed, + skipped.length, + buildOnboardingDashboardUrl(auth.serverUrl, resolution.orgKey), + ); } diff --git a/tests/integration/specs/import/import.test.ts b/tests/integration/specs/import/import.test.ts index e71a0e001..639377ae3 100644 --- a/tests/integration/specs/import/import.test.ts +++ b/tests/integration/specs/import/import.test.ts @@ -413,6 +413,9 @@ describe('sonar import', () => { ); expect(result.stdout).toContain('Manual — choose repositories yourself'); expect(result.stdout).toContain('Imported 2 repositories'); + expect(result.stdout).toContain( + `Dashboard: ${serverUrl}/organizations/my-org/onboarding-dashboard`, + ); const recorded = server.getRecordedRequests(); const provisionRequests = recorded.filter( (r) => r.path === '/api/alm_integration/provision_projects', From 7c8e80153ecc4870619270997c3a73007d7fc1ed Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Tue, 21 Jul 2026 18:02:53 +0200 Subject: [PATCH 2/4] GROW-256 Render the dashboard link inside the outro box Show the onboarding dashboard link as a detail line within the success box instead of a separate info line above it, so `outro()` gains an optional `detail` line for other callers that want the same layout. Co-Authored-By: Claude Sonnet 5 --- src/commands/import/index.ts | 7 +++++-- src/ui/components/sections.ts | 15 +++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/commands/import/index.ts b/src/commands/import/index.ts index c424e1671..755b41194 100644 --- a/src/commands/import/index.ts +++ b/src/commands/import/index.ts @@ -202,8 +202,11 @@ function reportOutcome( } const succeededNoun = succeeded === 1 ? 'repository' : 'repositories'; - info(`Dashboard: ${dashboardUrl}`); - outro(`Imported ${succeeded} ${succeededNoun}${skippedSuffix}`, 'success'); + outro( + `Imported ${succeeded} ${succeededNoun}${skippedSuffix}`, + 'success', + `Dashboard: ${dashboardUrl}`, + ); } export async function importHandler(options: ImportOptions, auth: ResolvedAuth): Promise { diff --git a/src/ui/components/sections.ts b/src/ui/components/sections.ts index 8453d23cc..6bdde6231 100644 --- a/src/ui/components/sections.ts +++ b/src/ui/components/sections.ts @@ -20,7 +20,7 @@ // Structural markers — intro, outro -import { bold, green, isTTY, red } from '../colors.js'; +import { bold, dim, green, isTTY, red } from '../colors.js'; import { isMockActive, recordCall } from '../mock.js'; const DIVIDER_BASE_WIDTH = 40; @@ -44,9 +44,13 @@ export function intro(title: string, subtitle?: string): void { } } -export function outro(message: string, status: 'success' | 'error' = 'success'): void { +export function outro( + message: string, + status: 'success' | 'error' = 'success', + detail?: string, +): void { if (isMockActive()) { - recordCall('outro', message, status); + recordCall('outro', message, status, detail); return; } @@ -56,8 +60,11 @@ export function outro(message: string, status: 'success' | 'error' = 'success'): if (isTTY) { process.stdout.write(`\n ${DIVIDER}\n`); process.stdout.write(` ${icon} ${bold(colorFn(message))}\n`); + if (detail) process.stdout.write(` ${dim(detail)}\n`); process.stdout.write(` ${DIVIDER}\n\n`); } else { - process.stdout.write(`\n=== ${message} ===\n\n`); + process.stdout.write(`\n=== ${message} ===\n`); + if (detail) process.stdout.write(`${detail}\n`); + process.stdout.write('\n'); } } From c5b4418d3db7b41a77935ca5ff270405991b27e5 Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Tue, 21 Jul 2026 18:35:22 +0200 Subject: [PATCH 3/4] GROW-256 Bold the outro detail line so it stands out The dashboard link was dimmed, making it easy to miss next to the bold success message. Bold + cyan gives it equal visual weight. Co-Authored-By: Claude Sonnet 5 --- src/ui/components/sections.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/components/sections.ts b/src/ui/components/sections.ts index 6bdde6231..a2de9971a 100644 --- a/src/ui/components/sections.ts +++ b/src/ui/components/sections.ts @@ -20,7 +20,7 @@ // Structural markers — intro, outro -import { bold, dim, green, isTTY, red } from '../colors.js'; +import { bold, cyan, green, isTTY, red } from '../colors.js'; import { isMockActive, recordCall } from '../mock.js'; const DIVIDER_BASE_WIDTH = 40; @@ -60,7 +60,7 @@ export function outro( if (isTTY) { process.stdout.write(`\n ${DIVIDER}\n`); process.stdout.write(` ${icon} ${bold(colorFn(message))}\n`); - if (detail) process.stdout.write(` ${dim(detail)}\n`); + if (detail) process.stdout.write(` ${bold(cyan(detail))}\n`); process.stdout.write(` ${DIVIDER}\n\n`); } else { process.stdout.write(`\n=== ${message} ===\n`); From 77582c5713116030b58d94c29f58183bed3a19a2 Mon Sep 17 00:00:00 2001 From: Kevin Silva Date: Tue, 21 Jul 2026 18:39:29 +0200 Subject: [PATCH 4/4] GROW-256 Align the outro detail line under the message text Indent the detail line by 6 spaces (2 leading + 2-col emoji icon + 2 gap) so it lines up under the message text instead of the icon. Co-Authored-By: Claude Sonnet 5 --- src/ui/components/sections.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/components/sections.ts b/src/ui/components/sections.ts index a2de9971a..63cf8f231 100644 --- a/src/ui/components/sections.ts +++ b/src/ui/components/sections.ts @@ -60,7 +60,8 @@ export function outro( if (isTTY) { process.stdout.write(`\n ${DIVIDER}\n`); process.stdout.write(` ${icon} ${bold(colorFn(message))}\n`); - if (detail) process.stdout.write(` ${bold(cyan(detail))}\n`); + // Aligns under the message text: 2 leading + 2-col emoji icon + 2 gap. + if (detail) process.stdout.write(` ${bold(cyan(detail))}\n`); process.stdout.write(` ${DIVIDER}\n\n`); } else { process.stdout.write(`\n=== ${message} ===\n`);