Skip to content

Commit 3a4304e

Browse files
feat(brew): add GetInstalledTaps and Untap
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 517c464 commit 3a4304e

3 files changed

Lines changed: 174 additions & 0 deletions

File tree

internal/brew/brew.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,52 @@ func UninstallCask(packages []string, dryRun bool) error {
652652
return nil
653653
}
654654

655+
func GetInstalledTaps() ([]string, error) {
656+
cmd := exec.Command("brew", "tap")
657+
output, err := cmd.Output()
658+
if err != nil {
659+
return nil, fmt.Errorf("brew tap: %w", err)
660+
}
661+
var taps []string
662+
for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
663+
line = strings.TrimSpace(line)
664+
if line != "" {
665+
taps = append(taps, line)
666+
}
667+
}
668+
return taps, nil
669+
}
670+
671+
func Untap(taps []string, dryRun bool) error {
672+
if len(taps) == 0 {
673+
return nil
674+
}
675+
676+
if dryRun {
677+
ui.Info("Would remove taps:")
678+
for _, t := range taps {
679+
fmt.Printf(" brew untap %s\n", t)
680+
}
681+
return nil
682+
}
683+
684+
var failed []string
685+
for _, tap := range taps {
686+
cmd := exec.Command("brew", "untap", tap)
687+
if output, err := cmd.CombinedOutput(); err != nil {
688+
ui.Warn(fmt.Sprintf("Failed to remove tap %s: %s", tap, strings.TrimSpace(string(output))))
689+
failed = append(failed, tap)
690+
} else {
691+
ui.Success(fmt.Sprintf(" ✔ Removed tap %s", tap))
692+
}
693+
}
694+
695+
if len(failed) > 0 {
696+
return fmt.Errorf("%d tap(s) failed to remove", len(failed))
697+
}
698+
return nil
699+
}
700+
655701
func Update(dryRun bool) error {
656702
if dryRun {
657703
ui.Info("Would run: brew update && brew upgrade")

internal/brew/brew_command_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,119 @@ func TestUpdateAndCleanup_UsesBrew(t *testing.T) {
8989
err = Cleanup()
9090
assert.NoError(t, err)
9191
}
92+
93+
func TestUninstall_Empty(t *testing.T) {
94+
err := Uninstall([]string{}, false)
95+
assert.NoError(t, err)
96+
}
97+
98+
func TestUninstall_DryRun(t *testing.T) {
99+
err := Uninstall([]string{"git", "curl"}, true)
100+
assert.NoError(t, err)
101+
}
102+
103+
func TestUninstall_Success(t *testing.T) {
104+
setupFakeBrew(t, "#!/bin/sh\nexit 0\n")
105+
err := Uninstall([]string{"wget", "jq"}, false)
106+
assert.NoError(t, err)
107+
}
108+
109+
func TestUninstall_Failure(t *testing.T) {
110+
setupFakeBrew(t, "#!/bin/sh\necho 'Error: No such keg'\nexit 1\n")
111+
err := Uninstall([]string{"nonexistent"}, false)
112+
assert.Error(t, err)
113+
}
114+
115+
func TestUninstall_PartialFailure(t *testing.T) {
116+
setupFakeBrew(t, "#!/bin/sh\n"+
117+
"if [ \"$2\" = \"bad-pkg\" ]; then\n"+
118+
" echo 'Error: No such keg'\n"+
119+
" exit 1\n"+
120+
"fi\n"+
121+
"exit 0\n")
122+
err := Uninstall([]string{"good-pkg", "bad-pkg"}, false)
123+
assert.Error(t, err)
124+
}
125+
126+
func TestUninstallCask_Empty(t *testing.T) {
127+
err := UninstallCask([]string{}, false)
128+
assert.NoError(t, err)
129+
}
130+
131+
func TestUninstallCask_DryRun(t *testing.T) {
132+
err := UninstallCask([]string{"firefox", "slack"}, true)
133+
assert.NoError(t, err)
134+
}
135+
136+
func TestUninstallCask_Success(t *testing.T) {
137+
setupFakeBrew(t, "#!/bin/sh\nexit 0\n")
138+
err := UninstallCask([]string{"firefox"}, false)
139+
assert.NoError(t, err)
140+
}
141+
142+
func TestUninstallCask_Failure(t *testing.T) {
143+
setupFakeBrew(t, "#!/bin/sh\necho 'Error: Cask not found'\nexit 1\n")
144+
err := UninstallCask([]string{"nonexistent-cask"}, false)
145+
assert.Error(t, err)
146+
}
147+
148+
func TestDoctorDiagnose_ReadyToBrew(t *testing.T) {
149+
setupFakeBrew(t, "#!/bin/sh\nif [ \"$1\" = \"doctor\" ]; then\n echo 'Your system is ready to brew.'\n exit 0\nfi\nexit 0\n")
150+
suggestions, err := DoctorDiagnose()
151+
require.NoError(t, err)
152+
assert.Nil(t, suggestions)
153+
}
154+
155+
func TestDoctorDiagnose_Failure(t *testing.T) {
156+
setupFakeBrew(t, "#!/bin/sh\nif [ \"$1\" = \"doctor\" ]; then\n exit 1\nfi\nexit 0\n")
157+
_, err := DoctorDiagnose()
158+
assert.Error(t, err)
159+
}
160+
161+
func TestDoctorDiagnose_MultipleWarnings(t *testing.T) {
162+
setupFakeBrew(t, "#!/bin/sh\n"+
163+
"if [ \"$1\" = \"doctor\" ]; then\n"+
164+
" echo 'Warning: Unbrewed dylibs were found in /usr/local/lib'\n"+
165+
" echo 'Warning: Your Homebrew/homebrew/core tap is not a full clone'\n"+
166+
" echo 'Warning: Git origin remote mismatch'\n"+
167+
" echo 'Warning: Uncommitted modifications to Homebrew'\n"+
168+
" echo 'Warning: outdated Xcode command line tools'\n"+
169+
" echo 'Warning: Broken symlinks were found'\n"+
170+
" echo 'Warning: permission issues'\n"+
171+
" exit 0\n"+
172+
"fi\n"+
173+
"exit 0\n")
174+
suggestions, err := DoctorDiagnose()
175+
require.NoError(t, err)
176+
assert.NotEmpty(t, suggestions)
177+
assert.Contains(t, suggestions, "Run: brew doctor --list-checks and review linked libraries")
178+
assert.Contains(t, suggestions, "Run: brew untap homebrew/core homebrew/cask")
179+
assert.Contains(t, suggestions, "Run: brew update-reset")
180+
assert.Contains(t, suggestions, "Run: xcode-select --install")
181+
assert.Contains(t, suggestions, "Run: brew cleanup --prune=all")
182+
assert.Contains(t, suggestions, "Run: sudo chown -R $(whoami) $(brew --prefix)/*")
183+
}
184+
185+
func TestDoctorDiagnose_UnknownWarnings(t *testing.T) {
186+
setupFakeBrew(t, "#!/bin/sh\n"+
187+
"if [ \"$1\" = \"doctor\" ]; then\n"+
188+
" echo 'Warning: Some unknown issue'\n"+
189+
" exit 0\n"+
190+
"fi\n"+
191+
"exit 0\n")
192+
suggestions, err := DoctorDiagnose()
193+
require.NoError(t, err)
194+
assert.Contains(t, suggestions, "Run 'brew doctor' to see full diagnostic output")
195+
}
196+
197+
func TestBrewInstallCmd_SetsNoAutoUpdate(t *testing.T) {
198+
cmd := brewInstallCmd("install", "git")
199+
assert.Equal(t, []string{"brew", "install", "git"}, cmd.Args)
200+
found := false
201+
for _, env := range cmd.Env {
202+
if env == "HOMEBREW_NO_AUTO_UPDATE=1" {
203+
found = true
204+
}
205+
}
206+
assert.True(t, found, "expected HOMEBREW_NO_AUTO_UPDATE=1 in env")
207+
}

internal/brew/brew_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"strings"
55
"testing"
66

7+
"github.com/openbootdotdev/openboot/internal/ui"
78
"github.com/stretchr/testify/assert"
89
)
910

@@ -223,3 +224,14 @@ func TestIsInstalled(t *testing.T) {
223224
func TestMaxWorkersConstant(t *testing.T) {
224225
assert.Equal(t, 1, maxWorkers)
225226
}
227+
228+
func TestPrintBrewOutput_Lines(t *testing.T) {
229+
p := ui.NewStickyProgress(1)
230+
printBrewOutput("line one\nline two\n line three \n", p)
231+
}
232+
233+
func TestPrintBrewOutput_Empty(t *testing.T) {
234+
p := ui.NewStickyProgress(1)
235+
printBrewOutput("", p)
236+
printBrewOutput(" \n ", p)
237+
}

0 commit comments

Comments
 (0)