Skip to content

Commit 523daf6

Browse files
committed
Refactor CLI version command and remove obsolete test file. Further cleaning of code, simplifying the help command, and improving the plugin list command
- Removed `root_test_main_test.go` as it was no longer needed. - Introduced `version_cmd.go` to handle version information display, including tracking third-party dependencies. - Added tests for the new version command in `version_cmd_test.go`, ensuring correct output and behavior. - Updated `git.go` to replace custom logger interface with `zap.Logger` for consistency. - Modified smoke test for plugin listing to use the new `--format` flag instead of `--json`.
1 parent c66cd5c commit 523daf6

20 files changed

Lines changed: 1112 additions & 494 deletions

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ coverage
1313
# IDE files
1414
.idea
1515

16-
# Claude files
17-
/.claude
16+
# Claude worktrees
17+
/.claude/worktrees
1818

1919
# Qodana files
2020
qodana.yaml

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Cache failures are **non-fatal** — log a warning and continue without caching.
111111

112112
### Terminal Output
113113

114-
- Use `internal/cli/ansi.go` helpers (`ansiStyled`, `ansiWrap`, `stripANSI`) — never raw escape codes inline.
114+
- Use `internal/cli/render/ansi.go` helpers (`Style`, `Wrap`, `StripANSI`) — never raw escape codes inline.
115115
- Interactive TUI uses Bubbletea (`internal/cli/interactive.go`) with the `interactiveModel` interface.
116116
- SARIF output via `internal/output` — do not hand-craft SARIF JSON.
117117

internal/cli/ansi.go

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ import (
66
"go.uber.org/zap"
77
)
88

9-
type executionLogger interface {
10-
Info(msg string, fields ...zap.Field)
11-
Debug(msg string, fields ...zap.Field)
12-
Error(msg string, fields ...zap.Field)
13-
}
14-
159
func commandLogger(cmd *cobra.Command, options *globalOptions, name string) *zap.Logger {
1610
current := options.current()
1711
// In default mode (no verbosity flags), suppress log output so only the
Lines changed: 28 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,29 @@ import (
55
"path/filepath"
66
"strings"
77

8+
"github.com/bomly-dev/bomly-cli/internal/progress"
89
"github.com/bomly-dev/bomly-cli/internal/scan"
910
model "github.com/bomly-dev/bomly-cli/sdk"
1011
)
1112

13+
// newCommandProgress constructs a Progress sourcing its writer + TTY-detection
14+
// from the CLI's commandStreams.
15+
func newCommandProgress(streams commandStreams, label string) *progress.Progress {
16+
return progress.New(streams.notificationWriter(), streams.canRenderProgress(), label)
17+
}
18+
1219
// warningProgressChildren converts pipeline warnings into ⚠ children using
1320
// the warning source as Label and the message as Detail.
14-
func warningProgressChildren(warnings []scan.PipelineWarning) []progressChild {
15-
children := make([]progressChild, 0, len(warnings))
21+
func warningProgressChildren(warnings []scan.PipelineWarning) []progress.Child {
22+
children := make([]progress.Child, 0, len(warnings))
1623
for _, w := range warnings {
1724
label := w.Source
1825
if label == "" {
1926
label = "unknown"
2027
}
2128
detail := strings.ReplaceAll(w.Message, "\n", " ")
22-
children = append(children, progressChild{
23-
Icon: progressWarningMark,
29+
children = append(children, progress.Child{
30+
Icon: progress.WarningMark,
2431
Label: label,
2532
Detail: detail,
2633
})
@@ -30,8 +37,8 @@ func warningProgressChildren(warnings []scan.PipelineWarning) []progressChild {
3037

3138
// subprojectProgressChildren returns one child per resolved subproject showing
3239
// the relative path and ecosystem.
33-
func subprojectProgressChildren(results []model.DetectionResult) []progressChild {
34-
children := make([]progressChild, 0, len(results))
40+
func subprojectProgressChildren(results []model.DetectionResult) []progress.Child {
41+
children := make([]progress.Child, 0, len(results))
3542
for _, r := range results {
3643
label := r.SubprojectInfo.RelativePath
3744
if label == "" || label == "." {
@@ -44,14 +51,14 @@ func subprojectProgressChildren(results []model.DetectionResult) []progressChild
4451
if detail != "" {
4552
label += " (" + detail + ")"
4653
}
47-
children = append(children, progressChild{Label: label})
54+
children = append(children, progress.Child{Label: label})
4855
}
4956
return children
5057
}
5158

5259
// detectorProgressChildren groups results by detector name, sums the total
5360
// package count per detector, and returns children with ✔ icon.
54-
func detectorProgressChildren(results []model.DetectionResult) []progressChild {
61+
func detectorProgressChildren(results []model.DetectionResult) []progress.Child {
5562
type detectorInfo struct {
5663
name string
5764
packages int
@@ -74,73 +81,24 @@ func detectorProgressChildren(results []model.DetectionResult) []progressChild {
7481
}
7582
}
7683
}
77-
children := make([]progressChild, 0, len(order))
84+
children := make([]progress.Child, 0, len(order))
7885
for _, key := range order {
7986
info := index[key]
80-
children = append(children, progressChild{
81-
Icon: progressCheckMark,
87+
children = append(children, progress.Child{
88+
Icon: progress.CheckMark,
8289
Label: humanizeDetectorName(info.name),
8390
Detail: fmt.Sprintf("[%d packages]", info.packages),
8491
})
8592
}
8693
return children
8794
}
8895

89-
// licenseProgressChildren counts packages with license data grouped by source type and
90-
// returns children with ✔ icon and [N licenses] detail.
91-
func licenseProgressChildren(results []model.DetectionResult) []progressChild {
92-
type sourceInfo struct {
93-
name string
94-
packages map[string]struct{}
95-
}
96-
index := make(map[string]*sourceInfo)
97-
order := make([]string, 0)
98-
for _, r := range results {
99-
if r.Graphs == nil {
100-
continue
101-
}
102-
for _, entry := range r.Graphs.Entries {
103-
if entry.Graph == nil {
104-
continue
105-
}
106-
for _, pkg := range entry.Graph.Packages() {
107-
if pkg == nil {
108-
continue
109-
}
110-
for _, lic := range pkg.Licenses {
111-
key := lic.Type
112-
if key == "" {
113-
continue
114-
}
115-
info, exists := index[key]
116-
if !exists {
117-
info = &sourceInfo{name: key, packages: make(map[string]struct{})}
118-
index[key] = info
119-
order = append(order, key)
120-
}
121-
info.packages[pkg.ID] = struct{}{}
122-
}
123-
}
124-
}
125-
}
126-
children := make([]progressChild, 0, len(order))
127-
for _, key := range order {
128-
info := index[key]
129-
children = append(children, progressChild{
130-
Icon: progressCheckMark,
131-
Label: humanizeLicenseSource(info.name),
132-
Detail: fmt.Sprintf("[%d licenses]", len(info.packages)),
133-
})
134-
}
135-
return children
136-
}
137-
13896
// auditProgressChildren groups findings by source and returns children with ✔ icon.
139-
func auditProgressChildren(auditorRuns []string, auditorFindings map[string]int, warnings []scan.PipelineWarning) []progressChild {
140-
children := make([]progressChild, 0, len(auditorRuns)+len(warnings))
97+
func auditProgressChildren(auditorRuns []string, auditorFindings map[string]int, warnings []scan.PipelineWarning) []progress.Child {
98+
children := make([]progress.Child, 0, len(auditorRuns)+len(warnings))
14199
for _, name := range auditorRuns {
142-
children = append(children, progressChild{
143-
Icon: progressCheckMark,
100+
children = append(children, progress.Child{
101+
Icon: progress.CheckMark,
144102
Label: humanizeAuditorSource(name),
145103
Detail: fmt.Sprintf("[%d findings]", auditorFindings[name]),
146104
})
@@ -151,11 +109,11 @@ func auditProgressChildren(auditorRuns []string, auditorFindings map[string]int,
151109

152110
// matchProgressChildren returns ✔ children for each successful matcher run
153111
// and ⚠ children for each warning.
154-
func matchProgressChildren(g *model.Graph, runs []string, warnings []scan.PipelineWarning) []progressChild {
155-
children := make([]progressChild, 0, len(runs)+len(warnings))
112+
func matchProgressChildren(g *model.Graph, runs []string, warnings []scan.PipelineWarning) []progress.Child {
113+
children := make([]progress.Child, 0, len(runs)+len(warnings))
156114
for _, name := range runs {
157-
children = append(children, progressChild{
158-
Icon: progressCheckMark,
115+
children = append(children, progress.Child{
116+
Icon: progress.CheckMark,
159117
Label: humanizeMatcherName(name),
160118
Detail: matcherProgressDetail(g, name),
161119
})
@@ -177,11 +135,11 @@ func matcherProgressDetail(g *model.Graph, matcherName string) string {
177135
}
178136
switch matcherName {
179137
case depsdevCheckerName:
180-
if packageHasLicenseSource(pkg, "external-depsdev") {
138+
if packageHasLicenseSource(pkg, "deps.dev") {
181139
packages++
182140
}
183141
case clearlyDefinedCheckerName:
184-
if packageHasLicenseSource(pkg, "external-clearlydefined") {
142+
if packageHasLicenseSource(pkg, "ClearlyDefined") {
185143
packages++
186144
}
187145
case osvMatcherName, grypeMatcherName:
@@ -247,18 +205,6 @@ func humanizeDetectorName(name string) string {
247205
return strings.Join(parts, " ") + " Detector"
248206
}
249207

250-
// humanizeLicenseSource converts a license source type to a display name.
251-
func humanizeLicenseSource(sourceType string) string {
252-
switch sourceType {
253-
case "external-depsdev":
254-
return "deps.dev"
255-
case "external-clearlydefined":
256-
return "ClearlyDefined"
257-
default:
258-
return sourceType
259-
}
260-
}
261-
262208
// humanizeAuditorSource converts an auditor source name to a display name.
263209
func humanizeAuditorSource(source string) string {
264210
switch strings.ToLower(source) {
Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,70 +3,17 @@ package cli
33
import (
44
"testing"
55

6-
"github.com/bomly-dev/bomly-cli/internal/scan"
76
model "github.com/bomly-dev/bomly-cli/sdk"
87
)
98

10-
func TestLicenseProgressChildren_CountsPackagesWithLicensesPerSource(t *testing.T) {
11-
g := model.New()
12-
for _, pkg := range []*model.Package{
13-
model.NewPackage(model.Package{
14-
Name: "react",
15-
Version: "18.2.0",
16-
Licenses: []model.PackageLicense{{
17-
Type: "external-depsdev",
18-
Value: "MIT",
19-
}},
20-
}),
21-
model.NewPackage(model.Package{
22-
Name: "zod",
23-
Version: "3.23.0",
24-
Licenses: []model.PackageLicense{{
25-
Type: "external-depsdev",
26-
Value: "MIT",
27-
}},
28-
}),
29-
model.NewPackage(model.Package{
30-
Name: "chalk",
31-
Version: "5.4.1",
32-
Licenses: []model.PackageLicense{{
33-
Type: "external-clearlydefined",
34-
Value: "ISC",
35-
}},
36-
}),
37-
} {
38-
if err := g.AddPackage(pkg); err != nil {
39-
t.Fatalf("AddPackage() error = %v", err)
40-
}
41-
}
42-
43-
children := licenseProgressChildren([]model.DetectionResult{{
44-
Graphs: scan.SingleGraphContainer(g, model.ManifestMetadata{Path: "package.json", Kind: "npm"}),
45-
}})
46-
47-
if len(children) != 2 {
48-
t.Fatalf("expected 2 children, got %#v", children)
49-
}
50-
counts := make(map[string]string, len(children))
51-
for _, child := range children {
52-
counts[child.Label] = child.Detail
53-
}
54-
if counts["deps.dev"] != "[2 licenses]" {
55-
t.Fatalf("expected deps.dev count based on packages, got %#v", children)
56-
}
57-
if counts["ClearlyDefined"] != "[1 licenses]" {
58-
t.Fatalf("expected ClearlyDefined count based on packages, got %#v", children)
59-
}
60-
}
61-
629
func TestMatchProgressChildren_ReportsMatcherCounts(t *testing.T) {
6310
g := model.New()
6411
for _, pkg := range []*model.Package{
6512
model.NewPackage(model.Package{
6613
Name: "react",
6714
Version: "18.2.0",
6815
Licenses: []model.PackageLicense{{
69-
Type: "external-depsdev",
16+
Type: "deps.dev",
7017
Value: "MIT",
7118
}},
7219
Vulnerabilities: []model.PackageVulnerability{
@@ -78,7 +25,7 @@ func TestMatchProgressChildren_ReportsMatcherCounts(t *testing.T) {
7825
Name: "zod",
7926
Version: "3.23.0",
8027
Licenses: []model.PackageLicense{{
81-
Type: "external-clearlydefined",
28+
Type: "ClearlyDefined",
8229
Value: "Apache-2.0",
8330
}},
8431
Vulnerabilities: []model.PackageVulnerability{

internal/cli/flag_options.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,19 +211,7 @@ func optionValuesHelpSection(cmd *cobra.Command) string {
211211
return ""
212212
}
213213

214-
reg := scan.NewRegistry(scan.RegistryConfigs{}, *zap.NewNop())
215-
reg.Build()
216-
217-
var b strings.Builder
218-
b.WriteString("\n\n")
219-
b.WriteString(formatTable("Available Native Detectors", []string{"Ecosystem", "Package Managers", "Detectors (Alias)"}, buildNativeDetectorRows(reg)))
220-
b.WriteString("\n")
221-
b.WriteString(formatTable("Available Third-party Detectors", []string{"Detector", "Ecosystems"}, buildThirdPartyDetectorRows(reg)))
222-
b.WriteString("\n")
223-
b.WriteString(formatHelpList("Available Auditors", availableAuditorOptions(zap.NewNop())))
224-
b.WriteString("\n")
225-
b.WriteString(formatHelpList("Available Matchers", availableMatcherOptions()))
226-
return b.String()
214+
return "\n\nExplore available detectors, matchers, and auditors with `bomly plugin list`."
227215
}
228216

229217
func formatHelpList(label string, values []string) string {

internal/cli/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func (ctx commandContext) close() error {
244244
return ctx.cleanup()
245245
}
246246

247-
func (o *globalOptions) resolveExecutionTarget(logger executionLogger) (model.ExecutionTarget, string, func() error, error) {
247+
func (o *globalOptions) resolveExecutionTarget(logger *zap.Logger) (model.ExecutionTarget, string, func() error, error) {
248248
current := o.current()
249249
if current.SBOM {
250250
if current.Container != "" || current.URL != "" || current.Ref != "" {

0 commit comments

Comments
 (0)