Skip to content

Commit 501373c

Browse files
committed
refactor(cmd): remove defaultCLIContext global singleton, inject CLIContext via cobra context
1 parent 5f169ce commit 501373c

20 files changed

Lines changed: 313 additions & 429 deletions

cmd/completion_test.go

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010

1111
// TestCompletionCommandBash generates bash completion script.
1212
func TestCompletionCommandBash(t *testing.T) {
13-
originalConfigDir := defaultCLIContext.ConfigDir()
14-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
13+
originalConfigDir := testCLI.ConfigDir()
14+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
1515

1616
tmpDir := t.TempDir()
17-
defaultCLIContext.SetConfigDir(tmpDir)
17+
testCLI.SetConfigDir(tmpDir)
1818

1919
buf := new(bytes.Buffer)
2020
rootCmd.SetOut(buf)
@@ -42,11 +42,11 @@ func min(a, b int) int {
4242

4343
// TestCompletionCommandZsh generates zsh completion script.
4444
func TestCompletionCommandZsh(t *testing.T) {
45-
originalConfigDir := defaultCLIContext.ConfigDir()
46-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
45+
originalConfigDir := testCLI.ConfigDir()
46+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
4747

4848
tmpDir := t.TempDir()
49-
defaultCLIContext.SetConfigDir(tmpDir)
49+
testCLI.SetConfigDir(tmpDir)
5050

5151
buf := new(bytes.Buffer)
5252
rootCmd.SetOut(buf)
@@ -67,11 +67,11 @@ func TestCompletionCommandZsh(t *testing.T) {
6767

6868
// TestCompletionCommandFish generates fish completion script.
6969
func TestCompletionCommandFish(t *testing.T) {
70-
originalConfigDir := defaultCLIContext.ConfigDir()
71-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
70+
originalConfigDir := testCLI.ConfigDir()
71+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
7272

7373
tmpDir := t.TempDir()
74-
defaultCLIContext.SetConfigDir(tmpDir)
74+
testCLI.SetConfigDir(tmpDir)
7575

7676
buf := new(bytes.Buffer)
7777
rootCmd.SetOut(buf)
@@ -92,11 +92,11 @@ func TestCompletionCommandFish(t *testing.T) {
9292

9393
// TestCompletionCommandPowerShell generates PowerShell completion script.
9494
func TestCompletionCommandPowerShell(t *testing.T) {
95-
originalConfigDir := defaultCLIContext.ConfigDir()
96-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
95+
originalConfigDir := testCLI.ConfigDir()
96+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
9797

9898
tmpDir := t.TempDir()
99-
defaultCLIContext.SetConfigDir(tmpDir)
99+
testCLI.SetConfigDir(tmpDir)
100100

101101
buf := new(bytes.Buffer)
102102
rootCmd.SetOut(buf)
@@ -117,11 +117,11 @@ func TestCompletionCommandPowerShell(t *testing.T) {
117117

118118
// TestCompletionCommandUnknownShell returns error for unknown shell.
119119
func TestCompletionCommandUnknownShell(t *testing.T) {
120-
originalConfigDir := defaultCLIContext.ConfigDir()
121-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
120+
originalConfigDir := testCLI.ConfigDir()
121+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
122122

123123
tmpDir := t.TempDir()
124-
defaultCLIContext.SetConfigDir(tmpDir)
124+
testCLI.SetConfigDir(tmpDir)
125125

126126
buf := new(bytes.Buffer)
127127
rootCmd.SetOut(buf)
@@ -135,11 +135,11 @@ func TestCompletionCommandUnknownShell(t *testing.T) {
135135

136136
// TestCompletionCommandNoArgs shows error when run without shell argument.
137137
func TestCompletionCommandNoArgs(t *testing.T) {
138-
originalConfigDir := defaultCLIContext.ConfigDir()
139-
t.Cleanup(func() { defaultCLIContext.SetConfigDir(originalConfigDir) })
138+
originalConfigDir := testCLI.ConfigDir()
139+
t.Cleanup(func() { testCLI.SetConfigDir(originalConfigDir) })
140140

141141
tmpDir := t.TempDir()
142-
defaultCLIContext.SetConfigDir(tmpDir)
142+
testCLI.SetConfigDir(tmpDir)
143143

144144
buf := new(bytes.Buffer)
145145
rootCmd.SetOut(buf)
@@ -153,15 +153,15 @@ func TestCompletionCommandNoArgs(t *testing.T) {
153153

154154
// TestCompletionCommandWithOutputFlag saves completion to file.
155155
func TestCompletionCommandWithOutputFlag(t *testing.T) {
156-
originalConfigDir := defaultCLIContext.ConfigDir()
156+
originalConfigDir := testCLI.ConfigDir()
157157
t.Cleanup(func() {
158-
defaultCLIContext.SetConfigDir(originalConfigDir)
158+
testCLI.SetConfigDir(originalConfigDir)
159159
completionOutput = ""
160160
completionSave = false
161161
})
162162

163163
tmpDir := t.TempDir()
164-
defaultCLIContext.SetConfigDir(tmpDir)
164+
testCLI.SetConfigDir(tmpDir)
165165

166166
outputPath := filepath.Join(tmpDir, "kairo-completion.sh")
167167
rootCmd.SetArgs([]string{"completion", "bash", "--output", outputPath})
@@ -189,15 +189,15 @@ func TestCompletionCommandWithOutputFlag(t *testing.T) {
189189

190190
// TestCompletionCommandWithShortOutputFlag saves to file using -o flag.
191191
func TestCompletionCommandWithShortOutputFlag(t *testing.T) {
192-
originalConfigDir := defaultCLIContext.ConfigDir()
192+
originalConfigDir := testCLI.ConfigDir()
193193
t.Cleanup(func() {
194-
defaultCLIContext.SetConfigDir(originalConfigDir)
194+
testCLI.SetConfigDir(originalConfigDir)
195195
completionOutput = ""
196196
completionSave = false
197197
})
198198

199199
tmpDir := t.TempDir()
200-
defaultCLIContext.SetConfigDir(tmpDir)
200+
testCLI.SetConfigDir(tmpDir)
201201

202202
outputPath := filepath.Join(tmpDir, "kairo-completion.sh")
203203
rootCmd.SetArgs([]string{"completion", "bash", "-o", outputPath})
@@ -215,11 +215,11 @@ func TestCompletionCommandWithShortOutputFlag(t *testing.T) {
215215

216216
// TestCompletionCommandAutoSaveToDefaultLocation saves completion to default location with --save flag.
217217
func TestCompletionCommandAutoSaveToDefaultLocation(t *testing.T) {
218-
originalConfigDir := defaultCLIContext.ConfigDir()
218+
originalConfigDir := testCLI.ConfigDir()
219219
originalHome := os.Getenv("HOME")
220220
originalUserProfile := os.Getenv("USERPROFILE")
221221
t.Cleanup(func() {
222-
defaultCLIContext.SetConfigDir(originalConfigDir)
222+
testCLI.SetConfigDir(originalConfigDir)
223223
if originalHome != "" {
224224
os.Setenv("HOME", originalHome)
225225
} else {
@@ -235,7 +235,7 @@ func TestCompletionCommandAutoSaveToDefaultLocation(t *testing.T) {
235235
})
236236

237237
tmpDir := t.TempDir()
238-
defaultCLIContext.SetConfigDir(tmpDir)
238+
testCLI.SetConfigDir(tmpDir)
239239

240240
// Set both HOME and USERPROFILE for cross-platform compatibility
241241
os.Setenv("HOME", tmpDir)
@@ -376,11 +376,11 @@ func TestGetDefaultCompletionPathNoHomeDir(t *testing.T) {
376376
// TestCompletionPowerShellSaveWithRegisterArgumentCompleter verifies that PowerShell
377377
// --save flag writes the Register-ArgumentCompleter script instead of cobra's default
378378
func TestCompletionPowerShellSaveWithRegisterArgumentCompleter(t *testing.T) {
379-
originalConfigDir := defaultCLIContext.ConfigDir()
379+
originalConfigDir := testCLI.ConfigDir()
380380
originalHome := os.Getenv("HOME")
381381
originalUserProfile := os.Getenv("USERPROFILE")
382382
t.Cleanup(func() {
383-
defaultCLIContext.SetConfigDir(originalConfigDir)
383+
testCLI.SetConfigDir(originalConfigDir)
384384
if originalHome != "" {
385385
os.Setenv("HOME", originalHome)
386386
} else {
@@ -396,7 +396,7 @@ func TestCompletionPowerShellSaveWithRegisterArgumentCompleter(t *testing.T) {
396396
})
397397

398398
tmpDir := t.TempDir()
399-
defaultCLIContext.SetConfigDir(tmpDir)
399+
testCLI.SetConfigDir(tmpDir)
400400
os.Setenv("HOME", tmpDir)
401401
os.Setenv("USERPROFILE", tmpDir)
402402

cmd/context.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,25 +130,29 @@ func (c *CLIContext) DefaultProviderExplicit() bool {
130130
return c.defaultProviderExplicit
131131
}
132132

133-
// defaultCLIContext is the package-level singleton used by production.
134-
// It is mutated in place by Execute() through PersistentPreRun and the
135-
// verbose-flag wiring. Tests must not depend on it: use NewCLIContext()
136-
// to construct isolated contexts and inject them with SetDeps.
137-
//
138-
// Goroutine-safety: the singleton is safe for concurrent reads; writes
139-
// happen only at startup (PersistentPreRun) and via SetConfigDir /
140-
// SetVerbose / SetDeps (the latter two are for tests).
141-
var defaultCLIContext = NewCLIContext()
142-
143133
// CLIContextFromCmd extracts the CLIContext from a cobra command's context.
134+
// Returns nil if no CLIContext is set (callers should use MustCLIContextFromCmd
135+
// when a cmd is always available, or handle nil gracefully).
144136
func CLIContextFromCmd(cmd *cobra.Command) *CLIContext {
145137
if ctx := cmd.Context(); ctx != nil {
146138
if cliCtx, ok := ctx.Value(cliContextKey{}).(*CLIContext); ok {
147139
return cliCtx
148140
}
149141
}
150142

151-
return defaultCLIContext
143+
return nil
144+
}
145+
146+
// MustCLIContextFromCmd is like CLIContextFromCmd but panics if no CLIContext
147+
// is found. Use when a cobra command is guaranteed to have been initialized
148+
// via Execute().
149+
func MustCLIContextFromCmd(cmd *cobra.Command) *CLIContext {
150+
cliCtx := CLIContextFromCmd(cmd)
151+
if cliCtx == nil {
152+
panic("kairo: no CLIContext in command context; Execute() must be called first")
153+
}
154+
155+
return cliCtx
152156
}
153157

154158
// WithCLIContext stores a CLIContext in the given context.

cmd/default_test.go

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

1313
func TestDefaultCommandNoArgs(t *testing.T) {
14-
originalConfigDir := defaultCLIContext.ConfigDir()
15-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
14+
originalConfigDir := testCLI.ConfigDir()
15+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
1616

1717
tmpDir := t.TempDir()
18-
defaultCLIContext.SetConfigDir(tmpDir)
18+
testCLI.SetConfigDir(tmpDir)
1919

2020
configPath := filepath.Join(tmpDir, "config.yaml")
2121
configContent := `default_provider: zai
@@ -30,19 +30,19 @@ providers:
3030
t.Fatal(err)
3131
}
3232

33-
rootCmd.SetArgs([]string{"default"})
33+
rootCmd.SetArgs([]string{"--config", tmpDir, "default"})
3434
err = rootCmd.Execute()
3535
if err != nil {
3636
t.Fatalf("Execute() error = %v", err)
3737
}
3838
}
3939

4040
func TestDefaultCommandSetProvider(t *testing.T) {
41-
originalConfigDir := defaultCLIContext.ConfigDir()
42-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
41+
originalConfigDir := testCLI.ConfigDir()
42+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
4343

4444
tmpDir := t.TempDir()
45-
defaultCLIContext.SetConfigDir(tmpDir)
45+
testCLI.SetConfigDir(tmpDir)
4646

4747
configPath := filepath.Join(tmpDir, "config.yaml")
4848
configContent := `default_provider: anthropic
@@ -61,7 +61,7 @@ providers:
6161
t.Fatal(err)
6262
}
6363

64-
rootCmd.SetArgs([]string{"default", "zai"})
64+
rootCmd.SetArgs([]string{"--config", tmpDir, "default", "zai"})
6565
err = rootCmd.Execute()
6666
if err != nil {
6767
t.Fatalf("Execute() error = %v", err)
@@ -77,11 +77,11 @@ providers:
7777
}
7878

7979
func TestDefaultCommandProviderNotFound(t *testing.T) {
80-
originalConfigDir := defaultCLIContext.ConfigDir()
81-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
80+
originalConfigDir := testCLI.ConfigDir()
81+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
8282

8383
tmpDir := t.TempDir()
84-
defaultCLIContext.SetConfigDir(tmpDir)
84+
testCLI.SetConfigDir(tmpDir)
8585

8686
configPath := filepath.Join(tmpDir, "config.yaml")
8787
configContent := `default_provider: anthropic
@@ -96,19 +96,19 @@ providers:
9696
t.Fatal(err)
9797
}
9898

99-
rootCmd.SetArgs([]string{"default", "nonexistent"})
99+
rootCmd.SetArgs([]string{"--config", tmpDir, "default", "nonexistent"})
100100
err = rootCmd.Execute()
101101
if err != nil {
102102
t.Fatalf("Execute() error = %v", err)
103103
}
104104
}
105105

106106
func TestDefaultCommandUpdatesConfigFile(t *testing.T) {
107-
originalConfigDir := defaultCLIContext.ConfigDir()
108-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
107+
originalConfigDir := testCLI.ConfigDir()
108+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
109109

110110
tmpDir := t.TempDir()
111-
defaultCLIContext.SetConfigDir(tmpDir)
111+
testCLI.SetConfigDir(tmpDir)
112112

113113
configPath := filepath.Join(tmpDir, "config.yaml")
114114
configContent := `default_provider: anthropic
@@ -127,7 +127,7 @@ providers:
127127
t.Fatal(err)
128128
}
129129

130-
rootCmd.SetArgs([]string{"default", "zai"})
130+
rootCmd.SetArgs([]string{"--config", tmpDir, "default", "zai"})
131131
err = rootCmd.Execute()
132132
if err != nil {
133133
t.Fatalf("Execute() error = %v", err)

cmd/delete_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ func TestDeleteCmdDeletesProviderFromConfig(t *testing.T) {
2222
t.Fatalf("EnsureKeyExists() error = %v", err)
2323
}
2424

25-
originalConfigDir := defaultCLIContext.ConfigDir()
26-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
25+
originalConfigDir := testCLI.ConfigDir()
26+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
2727

28-
defaultCLIContext.SetConfigDir(tmpDir)
28+
testCLI.SetConfigDir(tmpDir)
2929

3030
cfg := &config.Config{
3131
DefaultProvider: "testprovider",
@@ -85,10 +85,10 @@ func TestDeleteCmdDeletesProviderSecrets(t *testing.T) {
8585
t.Fatalf("EnsureKeyExists() error = %v", err)
8686
}
8787

88-
originalConfigDir := defaultCLIContext.ConfigDir()
89-
defer func() { defaultCLIContext.SetConfigDir(originalConfigDir) }()
88+
originalConfigDir := testCLI.ConfigDir()
89+
defer func() { testCLI.SetConfigDir(originalConfigDir) }()
9090

91-
defaultCLIContext.SetConfigDir(tmpDir)
91+
testCLI.SetConfigDir(tmpDir)
9292

9393
cfg := &config.Config{
9494
DefaultProvider: "provider1",

cmd/execution_error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func promptUpgrade(cmd *cobra.Command, err error) {
6161
cmd.Println(" For manual installation, see:")
6262
cmd.Printf(" %s#manual-installation\n", constants.GitHubBlobURL("main", "docs/guides/user-guide.md"))
6363
cmd.Println()
64-
if verbose() {
64+
if verbose(cmd) {
6565
cmd.Printf("Technical details: %v\n", err)
6666
}
6767
}

cmd/execution_harness.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ func runHarnessExec(cfg ExecutionConfig, harnessPath string, cliArgs []string) e
4343
})
4444
}
4545

46-
ctx, cancel, stopSig := execution.StartSession(CLIContextFromCmd(cfg.Cmd).RootCtx())
46+
rootCtx := context.Background()
47+
if cliCtx := CLIContextFromCmd(cfg.Cmd); cliCtx != nil {
48+
rootCtx = cliCtx.RootCtx()
49+
}
50+
51+
ctx, cancel, stopSig := execution.StartSession(rootCtx)
4752
defer cancel()
4853
defer stopSig()
4954

@@ -161,7 +166,11 @@ func executeWithAuth(cfg ExecutionConfig) {
161166
}
162167

163168
func executeWrapperWithAuth(cfg ExecutionConfig) {
164-
ctx, cancel, stopSig := execution.StartSession(CLIContextFromCmd(cfg.Cmd).RootCtx())
169+
rootCtx := context.Background()
170+
if cliCtx := CLIContextFromCmd(cfg.Cmd); cliCtx != nil {
171+
rootCtx = cliCtx.RootCtx()
172+
}
173+
ctx, cancel, stopSig := execution.StartSession(rootCtx)
165174
defer cancel()
166175
defer stopSig()
167176

0 commit comments

Comments
 (0)