Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6632960
fix(tui): migrate to Bubble Tea v2 API
nicholas-fedor Feb 28, 2026
b74f8a1
ci(workflows): use go.mod for Go version management
nicholas-fedor Feb 28, 2026
64e37f0
chore: clean up configuration files and documentation
nicholas-fedor Feb 28, 2026
4e0d779
chore: update linting configuration and fix compliance issues
nicholas-fedor Feb 28, 2026
2e3a376
test(fs): fix cross-platform path handling in tests
nicholas-fedor Feb 28, 2026
318c139
Update .gitignore
nicholas-fedor Feb 28, 2026
35fed4f
test(fs): fix cross-platform path handling
nicholas-fedor Feb 28, 2026
ea5fe4b
Merge branch 'fix/bubbletea-v2-api-migration' of https://github.com/n…
nicholas-fedor Feb 28, 2026
0750027
refactor(logger): migrate from zap to zerolog
nicholas-fedor Feb 28, 2026
43d309a
Merge branch 'main' into refactor/zerolog
nicholas-fedor Feb 28, 2026
af33253
refactor(zerolog): enable zerologlint and fix dispatch violations
nicholas-fedor Mar 2, 2026
40ae882
fix(tui): refresh grid layout on log events
nicholas-fedor Mar 2, 2026
ab5e50c
test(logger): fix race condition in concurrent access test
nicholas-fedor Mar 2, 2026
b3f1213
fix(logger): fix race condition in log capture
nicholas-fedor Mar 2, 2026
89c917a
Merge branch 'main' into refactor/zerolog
nicholas-fedor Mar 2, 2026
fb66b36
refactor(logger): simplify io.Discard error handling
nicholas-fedor Mar 2, 2026
d8682fb
refactor(logger): scan for log levels instead of fixed position
nicholas-fedor Mar 2, 2026
f90d310
refactor(cli): remove unused program field from TUI model
nicholas-fedor Mar 2, 2026
bf5f740
test(cli): refactor to use generated mocks
nicholas-fedor Mar 2, 2026
cc52925
test(fs): simplify zerolog mock setup in tests
nicholas-fedor Mar 2, 2026
3772006
test(cli): refactor test helpers and improve mock integration
nicholas-fedor Mar 2, 2026
b91d891
fix(logger): fix race condition and message extraction
nicholas-fedor Mar 2, 2026
b8bc2cd
test(cli): refactor test structure and remove dead code
nicholas-fedor Mar 2, 2026
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
9 changes: 5 additions & 4 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ linters:
- varnamelen # Checks that the length of a variable's name matches its scope.
- wastedassign # Finds wasted assignment statements.
- wrapcheck # Checks that errors returned from external packages are wrapped.
- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`.
disable:
- cyclop # Checks function and package cyclomatic complexity.
- depguard # Checks if package imports are in a list of acceptable packages.
Expand All @@ -125,7 +126,6 @@ linters:
- tagliatelle # Checks the struct tags.
- testableexamples # Linter checks if examples are testable (have an expected output). [fast]
- testpackage # Linter that makes you use a separate _test package.
- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg`.

######################################################################################################
# Linter Settings Configuration
Expand All @@ -140,10 +140,10 @@ linters:
- name: var-naming
severity: warning
disabled: false
exclude: [""]
exclude: []
arguments:
- [""] # AllowList
- [""] # DenyList
- [] # AllowList
- [] # DenyList
- - skip-initialism-name-checks: false
upper-case-const: true
skip-package-name-checks: true
Expand Down Expand Up @@ -232,6 +232,7 @@ linters:
- promlinter
- wrapcheck
- varnamelen
- zerologlint

# Run some linter only for test files by excluding its issues for everything else.
# - path-except: _test\.go
Expand Down
87 changes: 36 additions & 51 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,20 @@ import (
"os"

"github.com/spf13/cobra"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"

"github.com/nicholas-fedor/go-remove/internal/cli"
"github.com/nicholas-fedor/go-remove/internal/fs"
"github.com/nicholas-fedor/go-remove/internal/logger"
)

// ErrInvalidLoggerType indicates that the logger is not of the expected *ZapLogger type.
var ErrInvalidLoggerType = errors.New("logger is not a *ZapLogger")
// ErrInvalidLoggerType indicates that the logger is not of the expected *ZerologLogger type.
var ErrInvalidLoggerType = errors.New("logger is not a *ZerologLogger")

// rootCmd defines the root command for go-remove.
var rootCmd = &cobra.Command{
Use: "go-remove [binary]",
Short: "A tool to remove Go binaries",
RunE: func(cmd *cobra.Command, args []string) error {
// Initialize the logger for application-wide logging.
log, err := logger.NewZapLogger()
if err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}

// Assemble dependencies with a real filesystem and the logger instance.
deps := cli.Dependencies{
FS: fs.NewRealFS(),
Logger: log,
}

// Extract flag values to configure CLI behavior; defaults to TUI mode if no binary is given.
verbose, _ := cmd.Flags().GetBool("verbose")
goroot, _ := cmd.Flags().GetBool("goroot")
Expand All @@ -64,54 +50,53 @@ var rootCmd = &cobra.Command{
LogLevel: logLevel,
}

// Set log level based on config if verbose mode is enabled.
if verbose {
zapLogger, ok := log.(*logger.ZapLogger)
if !ok {
return fmt.Errorf(
"failed to set log level: %w with type %T",
ErrInvalidLoggerType,
log,
)
}
// If a binary name is provided as an argument, run in direct removal mode.
if len(args) > 0 {
config.Binary = args[0]

switch logLevel {
case "debug":
zapLogger.Logger = zapLogger.WithOptions(
zap.IncreaseLevel(zapcore.DebugLevel),
)
case "warn":
zapLogger.Logger = zapLogger.WithOptions(
zap.IncreaseLevel(zapcore.WarnLevel),
)
case "error":
zapLogger.Logger = zapLogger.WithOptions(
zap.IncreaseLevel(zapcore.ErrorLevel),
)
default:
zapLogger.Logger = zapLogger.WithOptions(
zap.IncreaseLevel(zapcore.InfoLevel),
)
// Initialize the standard logger for direct removal mode.
log, err := logger.NewLogger()
if err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}

log = zapLogger // Update the logger in deps
deps.Logger = log
}
// Set log level based on config if verbose mode is enabled.
if verbose {
level := logger.ParseLevel(logLevel)
log.Level(level)
}

// If a binary name is provided as an argument, run in direct removal mode.
if len(args) > 0 {
config.Binary = args[0]
// Assemble dependencies with a real filesystem and the logger instance.
deps := cli.Dependencies{
FS: fs.NewRealFS(),
Logger: log,
}

return cli.Run(deps, config)
}

// Otherwise, determine the binary directory and launch the TUI for interactive selection.
binDir, err := deps.FS.DetermineBinDir(config.Goroot)
// For TUI mode, we use a logger with capture support to display logs within the interface.
filesystem := fs.NewRealFS()

binDir, err := filesystem.DetermineBinDir(config.Goroot)
if err != nil {
return fmt.Errorf("failed to determine binary directory: %w", err)
}

return cli.RunTUI(binDir, config, deps.Logger, deps.FS, cli.DefaultRunner{})
// Initialize the logger with capture support for TUI mode.
log, _, err := logger.NewLoggerWithCapture()
if err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}

// Set log level based on config if verbose mode is enabled.
if verbose {
level := logger.ParseLevel(logLevel)
log.Level(level)
}

return cli.RunTUI(binDir, config, log, filesystem, cli.DefaultRunner{})
},
}

Expand Down
7 changes: 0 additions & 7 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"bytes"
"os"
"testing"

logmocks "github.com/nicholas-fedor/go-remove/internal/logger/mocks"
)

// TestRootCommand verifies the behavior of the root command.
Expand All @@ -42,9 +40,6 @@ func TestRootCommand(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock logger without Sync expectation for help flag test.
log := logmocks.NewMockLogger(t)

// Redirect stderr to capture output.
oldStderr := os.Stderr
r, w, _ := os.Pipe()
Expand Down Expand Up @@ -77,8 +72,6 @@ func TestRootCommand(t *testing.T) {
if gotStderr != tt.wantStderr {
t.Errorf("rootCmd.Execute() stderr = %q, want %q", gotStderr, tt.wantStderr)
}

log.AssertExpectations(t)
})
}
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ go 1.26.0
require (
charm.land/bubbletea/v2 v2.0.0
charm.land/lipgloss/v2 v2.0.0
github.com/rs/zerolog v1.34.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.1
)

require (
Expand All @@ -22,14 +22,15 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
22 changes: 16 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,34 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
Expand All @@ -49,17 +62,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
4 changes: 0 additions & 4 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package cli

import (
"errors"
"fmt"
"os"

"github.com/nicholas-fedor/go-remove/internal/fs"
"github.com/nicholas-fedor/go-remove/internal/logger"
)

// ErrInvalidLoggerType indicates that the logger is not of the expected *ZapLogger type.
var ErrInvalidLoggerType = errors.New("logger is not a *ZapLogger")

// Config holds command-line configuration options.
type Config struct {
Binary string // Binary name to remove; empty for TUI mode
Expand Down
Loading
Loading