Skip to content
Merged
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
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# sonacli
<p align="center">
<img src="sonacli-logo.png" alt="sonacli logo" width="200">
</p>

`sonacli` is a CLI for consuming SonarQube analysis reports.
<h1 align="center">sonacli</h1>

<p align="center">A CLI for consuming SonarQube analysis reports.</p>

<p align="center">
<a href="https://github.com/mshddev/sonacli/releases"><img src="https://img.shields.io/github/v/release/mshddev/sonacli?sort=semver" alt="Release"></a>
<a href="https://github.com/mshddev/sonacli/actions/workflows/ci.yml"><img src="https://github.com/mshddev/sonacli/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/mshddev/sonacli" alt="License"></a>
</p>

<p align="center">
<img src="docs/demo.gif" alt="sonacli demo — auth, project list, issue list with jq, severity filter, and SonarQube URL parsing" width="800">
</p>

> SonarQube is an open-source platform for continuous inspection of code quality and security. It ships in several editions: **Community Build** (free, open-source, single-node), **Server** (commercial), **Data Center** (high-availability), and **Cloud** (SaaS, hosted by Sonar). `sonacli` targets the Community Build only.

Expand Down
4 changes: 4 additions & 0 deletions docs/_demo-source/sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sonar.projectKey=payments-api
sonar.projectName=Payments API
sonar.sources=src
sonar.sourceEncoding=UTF-8
26 changes: 26 additions & 0 deletions docs/_demo-source/src/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
// Not compiled by the sonacli module — the parent directory is Go-ignored.
package payments

import (
"fmt"
"net/http"
)

const adminPassword = "replace-me-in-real-deployments"

const stripeSecretKey = "replace-me-in-real-deployments"

func Authenticate(user, pass string) bool {
if user == "admin" && pass == adminPassword {
return true
}

return false
}

func CallStripe() {
req, _ := http.NewRequest("GET", "https://api.stripe.com/v1/charges", nil)
req.Header.Set("Authorization", "Bearer "+stripeSecretKey)
fmt.Println(req)
}
25 changes: 25 additions & 0 deletions docs/_demo-source/src/invoice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
// Not compiled by the sonacli module — the parent directory is Go-ignored.
package payments

import "fmt"

// TODO: migrate to the new billing engine before Q3
// FIXME: handle partial refunds
// TODO: add idempotency keys once the gateway supports them

func SendInvoice(id string) {
total := 0
total = 100
total = 200

fmt.Println("invoice", id, total)
}

func duplicate() {
fmt.Println("dup")
}

func duplicate2() {
fmt.Println("dup")
}
59 changes: 59 additions & 0 deletions docs/_demo-source/src/pricing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
// Not compiled by the sonacli module — the parent directory is Go-ignored.
package payments

import "fmt"

func ComputeDiscount(tier string, quantity int, seasonal bool, vip bool, region string) float64 {
discount := 0.0

if tier == "gold" {
if quantity > 100 {
if seasonal {
if vip {
discount = 0.35
} else {
discount = 0.25
}
} else {
if region == "EU" {
discount = 0.20
} else {
discount = 0.15
}
}
} else {
if vip {
discount = 0.15
} else {
discount = 0.10
}
}
} else if tier == "silver" {
if quantity > 50 {
if seasonal {
discount = 0.15
} else {
discount = 0.10
}
} else {
if vip {
discount = 0.08
} else {
discount = 0.05
}
}
} else {
if quantity > 20 {
discount = 0.05
} else {
discount = 0.0
}
}

return discount
}

func LegacyPricing() {
fmt.Println("legacy path")
}
Binary file added docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 80 additions & 0 deletions docs/demo.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# sonacli demo tape — regenerates docs/demo.gif
#
# Prerequisites: make, Go 1.26+, podman, vhs, jq, curl, a cached
# SonarScanner CLI (see scripts/seed-sample-project.sh for one).
#
# Regenerate:
# 1. make build
# 2. ./scripts/local-sonarqube.sh start
# 3. Create and analyze the demo project (once per fresh SonarQube):
# curl -fsS -u admin:SonacliAdmin1@ -X POST \
# "http://127.0.0.1:9000/api/projects/create" \
# --data-urlencode "project=payments-api" \
# --data-urlencode "name=Payments API"
# SCANNER=tests/bin/sample-project-tools/sonar-scanner-*/bin/sonar-scanner
# SCAN_TOKEN=$(curl -fsS -u admin:SonacliAdmin1@ -X POST \
# "http://127.0.0.1:9000/api/user_tokens/generate" \
# --data-urlencode "name=demo-scan-$(date +%s)" | jq -r .token)
# ( cd docs/_demo-source && \
# SONAR_HOST_URL=http://127.0.0.1:9000 SONAR_TOKEN=$SCAN_TOKEN \
# "$PWD/../../$SCANNER" )
# 4. export TOKEN=$(curl -fsS -u admin:SonacliAdmin1@ -X POST \
# "http://127.0.0.1:9000/api/user_tokens/generate" \
# --data-urlencode "name=demo-$(date +%s)" | jq -r .token)
# 5. export ISSUE_KEY=$(curl -fsS -u admin:SonacliAdmin1@ \
# "http://127.0.0.1:9000/api/issues/search?componentKeys=payments-api&impactSeverities=HIGH" \
# | jq -r '.issues[0].key')
# 6. vhs docs/demo.tape

Output docs/demo.gif

Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 760
Set Theme "Dracula"
Set TypingSpeed 30ms
Set PlaybackSpeed 1.0
Set Padding 25
Set WindowBar Colorful

Hide
Type 'export PATH="$PWD:$PATH" HOME=/tmp/sonacli-demo-home PS1="$ "'
Enter
Type 'mkdir -p $HOME && rm -rf $HOME/.sonacli && cd /tmp && clear'
Enter
Show

Sleep 600ms

# Scene 1 — Connect sonacli to a SonarQube instance.
Type "sonacli auth setup -s http://127.0.0.1:9000 -t $TOKEN"
Sleep 300ms
Enter
Sleep 1500ms

# Scene 2 — Browse projects as pretty JSON.
Type "sonacli project list --pretty"
Sleep 300ms
Enter
Sleep 1800ms

# Scene 3 — sonacli speaks JSON: compose with jq to reshape issues.
Type "sonacli issue list payments-api | jq '.issues[] | {rule, sev: .impacts[0].severity, file: .component, line, message}'"
Sleep 300ms
Enter
Sleep 2200ms

# Scene 4 — Filter by impact severity.
Type "sonacli issue list payments-api --severity HIGH | jq '.issues[] | {rule, file: .component, line, message}'"
Sleep 300ms
Enter
Sleep 2200ms

# Scene 5 — Paste a SonarQube web URL; sonacli extracts the issue key for you.
Type@6ms 'sonacli issue show "http://127.0.0.1:9000/project/issues?id=payments-api&issues=$ISSUE_KEY" | jq "{rule, file: .component, line, impact: .impacts[0], message}"'
Sleep 300ms
Enter
Sleep 2500ms

Sleep 500ms
Binary file added sonacli-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.