Skip to content

feat: SonarQube Server integration with PAT auth#12

Merged
kolatts merged 2 commits intomainfrom
feat/sonarqube-integration
Apr 6, 2026
Merged

feat: SonarQube Server integration with PAT auth#12
kolatts merged 2 commits intomainfrom
feat/sonarqube-integration

Conversation

@kolatts
Copy link
Copy Markdown
Owner

@kolatts kolatts commented Apr 6, 2026

Summary

  • Implements full SonarQube Server (Data Center) integration following the existing 4-layer pattern (types → config → http → client → commands)
  • 5 new commands: sonar quality-gate, sonar issues, sonar measures, sonar projects, sonar hotspots
  • PAT Bearer auth via PNCLI_SONAR_TOKEN env var or pncli config init wizard
  • defaults.sonar.project config key makes --project optional on all project-scoped commands (same pattern as defaults.jira.project)
  • sonarPaginate helper handles SonarQube's page-number-based pagination; all commands support --all for auto-pagination
  • config test now checks SonarQube connectivity
  • Fixes shared error parser to handle SonarQube's {errors:[{msg}]} shape (other APIs use message)

Test plan

  • npm run typecheck passes
  • npm run lint passes
  • pncli sonar --help shows all 5 subcommands
  • pncli config show includes masked sonar block
  • pncli sonar quality-gate --project my-proj --dry-run prints request URL/headers without executing
  • pncli config test reports sonar: { ok: true } against a real SonarQube instance
  • pncli config init presents SonarQube section and writes to config

🤖 Generated with Claude Code

Sunny Kolattukudy and others added 2 commits April 5, 2026 22:50
…onfig test

- http.ts: read response.text() before JSON.parse so empty 200 bodies (common
  on Data Center PUT/DELETE) return undefined instead of crashing with
  SyntaxError: Unexpected end of JSON input
- http.ts: fix dry-run exit in jira/bitbucket/confluence — replace async
  stderr callback + pending Promise with fs.writeSync + process.exitCode so
  Node drains streams naturally; also removes the need for a hung promise
- output.ts: replace process.exit() in fail() with process.exitCode + throw
  so stderr flushes before Node exits; throw propagates to cli.ts catch handler
- cli.ts: top-level catch now skips "Fatal:" when exitCode is already set,
  preventing double-write when fail() or dry-run threw to bubble up
- config/commands.ts: replace all process.exit() in wizard cancellation paths
  with process.exitCode + return for the same stream-drain reason
- config test: implement real connectivity checks (Jira /myself, Bitbucket
  /application-properties, Confluence /space) instead of Phase 2 stub message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements full SonarQube Server (Data Center) integration following
the existing 4-layer pattern (types/config/http/client/commands).

- New commands: sonar quality-gate, issues, measures, projects, hotspots
- PAT Bearer auth via PNCLI_SONAR_TOKEN / pncli config init
- defaults.sonar.project config for project-key fallback (--project optional)
- sonarPaginate helper for page-number-based pagination
- config test now checks SonarQube connectivity
- Fixes shared error parser to handle SonarQube's {errors:[{msg}]} shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 6, 2026 03:09
@kolatts kolatts merged commit 1428bea into main Apr 6, 2026
3 checks passed
@kolatts kolatts deleted the feat/sonarqube-integration branch April 6, 2026 03:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds full SonarQube Server integration to pncli, including configuration, HTTP plumbing, a typed client, and new CLI commands for quality gate/metrics/issues/projects/hotspots.

Changes:

  • Introduces SonarQube API types and a SonarClient with page-number pagination support.
  • Adds sonar config + env var support (PNCLI_SONAR_BASE_URL, PNCLI_SONAR_TOKEN) and defaults.sonar.project to make --project optional.
  • Expands CLI surface area: 5 new pncli sonar ... commands, config init wizard prompts, and config test connectivity checks (incl. Sonar).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/types/sonar.ts Adds SonarQube Server Web API response types used by the client/commands.
src/types/config.ts Extends config types to include sonar settings and sonar defaults.
src/services/sonar/commands.ts Implements the pncli sonar command group with 5 subcommands and --all pagination.
src/services/sonar/client.ts Adds Sonar client wrapper around the HTTP layer for Sonar endpoints + pagination helpers.
src/services/config/commands.ts Updates config init wizard to include Sonar; implements real config test connectivity checks.
src/lib/output.ts Changes fail() to set process.exitCode and throw after emitting an error envelope.
src/lib/http.ts Adds Sonar auth + request method and sonarPaginate; adjusts dry-run behavior and error parsing.
src/lib/config.ts Loads Sonar config from env/global config; masks Sonar token in config show.
src/cli.ts Updates help text and global error handler behavior for new exit-code-based flow.
README.md Documents Sonar support and new env vars; updates service status table.
copilot-instructions.md Updates agent usage docs to include Sonar workflows and command reference.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/http.ts
Comment on lines 165 to 172
if (this.dryRun) {
const safeHeaders = { ...headers, Authorization: '[REDACTED]' };
const msg = `DRY RUN: ${init.method} ${url}\nHeaders: ${JSON.stringify(safeHeaders, null, 2)}\n`
+ (opts.body ? `Body: ${JSON.stringify(opts.body, null, 2)}\n` : '');
process.stderr.write(msg, () => process.exit(ExitCode.SUCCESS));
return new Promise<never>(() => { /* exit pending */ });
fs.writeSync(process.stderr.fd, msg);
process.exitCode = ExitCode.SUCCESS;
throw new PncliError('dry-run', 0);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dry-run requests currently throw PncliError('dry-run', 0). Because status=0 maps to ExitCode.NETWORK_ERROR and most commands wrap handlers with catch (...) { fail(...) }, --dry-run ends up being reported as an error (and may exit non-zero) instead of a successful early-exit. Consider throwing a dedicated sentinel error (e.g., DryRunError) or using a distinct PncliError status, and ensure the error path bypasses fail() / exit-code mapping for dry-run control-flow.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/output.ts
Comment on lines 39 to 72
export function fail(
err: unknown,
service: string,
action: string,
startTime: number
): never {
const errorDetail: ErrorDetail = {
status: err instanceof PncliError ? err.status : 1,
message: err instanceof Error ? err.message : String(err),
url: err instanceof PncliError ? (err.url ?? null) : null
};

const envelope: ErrorEnvelope = {
ok: false,
error: errorDetail,
meta: buildMeta(service, action, startTime)
};

const msg = globalOptions.pretty
? chalk.red('✗ Error: ') + errorDetail.message
: null;

if (msg) process.stderr.write(msg + '\n');

const output = (globalOptions.pretty ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope)) + '\n';
const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
try {
fs.writeSync(process.stdout.fd, output);
} catch (writeErr) {
if ((writeErr as NodeJS.ErrnoException).code !== 'EPIPE') throw writeErr;
}
process.exit(exitCode);
process.exitCode = exitCode;
throw new PncliError(errorDetail.message, errorDetail.status);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail() now always writes an error envelope and maps PncliError.status to an exit code. This interacts badly with the new dry-run flow (which throws PncliError('dry-run', 0)): dry-run becomes a printed error and can overwrite the intended success exit code. Add a special-case to treat dry-run as control-flow (e.g., rethrow without emitting an error envelope / without changing process.exitCode), or handle a dedicated DryRunError type here.

Copilot uses AI. Check for mistakes.
try {
const opts = program.optsWithGlobals();
const cfg = loadConfig({ configPath: opts.config });
const http = createHttpClient(cfg);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config test creates the HTTP client without passing the global --dry-run flag, so pncli config test --dry-run will still execute real requests (and won’t print the request details). Pass Boolean(opts.dryRun) into createHttpClient for consistency with other commands.

Suggested change
const http = createHttpClient(cfg);
const http = createHttpClient(cfg, Boolean(opts.dryRun));

Copilot uses AI. Check for mistakes.
process.stderr.write('Aborted.\n');
process.exit(ExitCode.SUCCESS);
process.exitCode = ExitCode.SUCCESS;
return;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return; inside the .pncli.json already exists / !overwrite branch is mis-indented, which is likely to fail linting/formatting checks and makes the control flow harder to read. Align indentation with the surrounding block.

Suggested change
return;
return;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants