Skip to content

Commit a0e469a

Browse files
cvclaude
andcommitted
refactor: Replace CLI globals with context-based CLIConfig struct
- Move Version to cmd/mcs/main.go (set by ldflags) - Create CLIConfig struct with Version, ConfigFile, NoColor fields - Use context.Value pattern to pass config through command tree - Update all tests to use context-based configuration - Remove gochecknoglobals exclusion for root.go - Add exclusion for main.go Version (required for ldflags) This improves testability by eliminating shared mutable state. Tests that use t.Setenv cannot run in parallel, but other tests can. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5041ec2 commit a0e469a

12 files changed

Lines changed: 256 additions & 185 deletions

File tree

.golangci.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ linters:
8484
- goconst
8585
- forcetypeassert
8686
# Global variables - justified exclusions:
87-
# 1. Read-only lookup tables (RegionConfigs, screenSizes, androidVersionToSDK)
87+
# 1. Version set by ldflags at build time
88+
- path: cmd/mcs/main\.go
89+
linters:
90+
- gochecknoglobals
91+
text: Version
92+
93+
# 3. Read-only lookup tables (RegionConfigs, screenSizes, androidVersionToSDK)
8894
- path: internal/api/auth\.go
8995
linters:
9096
- gochecknoglobals
@@ -94,19 +100,13 @@ linters:
94100
- gochecknoglobals
95101
text: (screenSizes|androidVersionToSDK)
96102

97-
# 2. CLI flags bound by Cobra framework (Version, ConfigFile, NoColor)
98-
- path: internal/cli/root\.go
99-
linters:
100-
- gochecknoglobals
101-
text: (Version|ConfigFile|NoColor)
102-
103-
# 3. Color state management - global state needed for CLI output formatting
103+
# 4. Color state management - global state needed for CLI output formatting
104104
- path: internal/cli/color\.go
105105
linters:
106106
- gochecknoglobals
107107
text: (colorEnabled|colorMu)
108108

109-
# 4. Test helper mutex for serializing parallel tests
109+
# 5. Test helper mutex for serializing parallel tests
110110
- path: internal/cli/color_test\.go
111111
linters:
112112
- gochecknoglobals

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ builds:
1717
- windows
1818
- darwin
1919
ldflags:
20-
- -s -w -X github.com/cv/mcs/internal/cli.Version={{.Version}}
20+
- -s -w -X main.Version={{.Version}}
2121

2222
archives:
2323
- formats: [tar.gz]

cmd/mcs/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"github.com/cv/mcs/internal/cli"
88
)
99

10+
// Version is set at build time via ldflags.
11+
var Version = "dev"
12+
1013
func main() {
11-
if err := cli.Execute(); err != nil {
14+
if err := cli.Execute(Version); err != nil {
1215
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
1316
os.Exit(1)
1417
}

internal/cli/cli_config.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cli
2+
3+
import "context"
4+
5+
// CLIConfig holds CLI configuration that was previously stored in package-level globals.
6+
// Using a struct allows tests to run in parallel without race conditions.
7+
type CLIConfig struct {
8+
// Version is the CLI version, set at build time via ldflags.
9+
Version string
10+
11+
// ConfigFile is the path to the config file, set via --config flag.
12+
ConfigFile string
13+
14+
// NoColor disables colored output, set via --no-color flag.
15+
NoColor bool
16+
}
17+
18+
// cliConfigKey is the context key for CLIConfig.
19+
type cliConfigKey struct{}
20+
21+
// ConfigFromContext retrieves the CLIConfig from the context.
22+
// Returns nil if no config is stored in the context.
23+
func ConfigFromContext(ctx context.Context) *CLIConfig {
24+
if cfg, ok := ctx.Value(cliConfigKey{}).(*CLIConfig); ok {
25+
return cfg
26+
}
27+
28+
return nil
29+
}
30+
31+
// ContextWithConfig returns a new context with the CLIConfig attached.
32+
func ContextWithConfig(ctx context.Context, cfg *CLIConfig) context.Context {
33+
return context.WithValue(ctx, cliConfigKey{}, cfg)
34+
}

internal/cli/client.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ import (
1111
)
1212

1313
// createAPIClient creates an API client with cached credentials if available.
14-
func createAPIClient() (*api.Client, error) {
15-
// Load configuration
16-
cfg, err := config.Load(ConfigFile)
14+
func createAPIClient(ctx context.Context) (*api.Client, error) {
15+
// Get CLI config from context.
16+
cliCfg := ConfigFromContext(ctx)
17+
configFile := ""
18+
if cliCfg != nil {
19+
configFile = cliCfg.ConfigFile
20+
}
21+
22+
// Load configuration.
23+
cfg, err := config.Load(configFile)
1724
if err != nil {
1825
return nil, fmt.Errorf("failed to load config: %w", err)
1926
}
@@ -79,7 +86,7 @@ type VehicleInfo struct {
7986
// setupVehicleClient is a shared helper that creates the API client and retrieves vehicle info.
8087
// It returns the authenticated client and full vehicle info, deferring cache save to the caller.
8188
func setupVehicleClient(ctx context.Context) (*api.Client, VehicleInfo, error) {
82-
client, err := createAPIClient()
89+
client, err := createAPIClient(ctx)
8390
if err != nil {
8491
return nil, VehicleInfo{}, err
8592
}

0 commit comments

Comments
 (0)