@@ -16,8 +16,6 @@ import (
1616 "github.com/openbootdotdev/openboot/internal/ui"
1717)
1818
19- const maxWorkers = 1
20-
2119type OutdatedPackage struct {
2220 Name string
2321 Current string
@@ -208,11 +206,11 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
208206
209207 if dryRun {
210208 ui .Info ("Would install packages:" )
211- for _ , p := range cliPkgs {
212- fmt .Printf (" brew install %s\n " , p )
209+ if len ( cliPkgs ) > 0 {
210+ fmt .Printf (" brew install %s\n " , strings . Join ( cliPkgs , " " ) )
213211 }
214- for _ , p := range caskPkgs {
215- fmt .Printf (" brew install --cask %s\n " , p )
212+ if len ( caskPkgs ) > 0 {
213+ fmt .Printf (" brew install --cask %s\n " , strings . Join ( caskPkgs , " " ) )
216214 }
217215 return nil , nil , nil
218216 }
@@ -261,36 +259,53 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
261259 var allFailed []failedJob
262260
263261 if len (newCli ) > 0 {
264- failed := runParallelInstallWithProgress (newCli , progress )
265- failedSet := make (map [string ]bool , len (failed ))
266- for _ , f := range failed {
267- failedSet [f .name ] = true
268- }
269- for _ , p := range newCli {
270- if ! failedSet [p ] {
271- installedFormulae = append (installedFormulae , p )
262+ ui .Info (fmt .Sprintf ("Installing %d CLI packages..." , len (newCli )))
263+
264+ args := append ([]string {"install" }, newCli ... )
265+ cmd := brewInstallCmd (args ... )
266+ cmd .Stdout = os .Stdout
267+ cmd .Stderr = os .Stderr
268+ err := cmd .Run ()
269+
270+ // Track as completed - we rely on brew's exit code for errors
271+ for _ , pkg := range newCli {
272+ progress .IncrementWithStatus (err == nil )
273+ if err == nil {
274+ installedFormulae = append (installedFormulae , pkg )
275+ } else {
276+ allFailed = append (allFailed , failedJob {
277+ installJob : installJob {name : pkg , isCask : false },
278+ errMsg : "install failed" ,
279+ })
272280 }
273281 }
274- allFailed = append (allFailed , failed ... )
275282 }
276283
277284 if len (newCask ) > 0 {
285+ ui .Info (fmt .Sprintf ("Installing %d GUI apps..." , len (newCask )))
286+
287+ args := append ([]string {"install" , "--cask" }, newCask ... )
288+ cmd := brewInstallCmd (args ... )
289+ cmd .Stdout = os .Stdout
290+ cmd .Stderr = os .Stderr
291+ // Open TTY for password prompts
292+ tty , opened := system .OpenTTY ()
293+ if opened {
294+ cmd .Stdin = tty
295+ }
296+ err := cmd .Run ()
297+ if opened {
298+ tty .Close ()
299+ }
300+
278301 for _ , pkg := range newCask {
279- progress .SetCurrent (pkg )
280- progress .PrintLine (" Installing %s..." , pkg )
281- start := time .Now ()
282- errMsg := installCaskWithProgress (pkg , progress )
283- elapsed := time .Since (start )
284- progress .IncrementWithStatus (errMsg == "" )
285- duration := ui .FormatDuration (elapsed )
286- if errMsg == "" {
287- progress .PrintLine (" %s %s" , ui .Green ("✔ " + pkg ), ui .Cyan ("(" + duration + ")" ))
302+ progress .IncrementWithStatus (err == nil )
303+ if err == nil {
288304 installedCasks = append (installedCasks , pkg )
289305 } else {
290- progress .PrintLine (" %s %s" , ui .Red ("✗ " + pkg + " (" + errMsg + ")" ), ui .Cyan ("(" + duration + ")" ))
291306 allFailed = append (allFailed , failedJob {
292307 installJob : installJob {name : pkg , isCask : true },
293- errMsg : errMsg ,
308+ errMsg : "install failed" ,
294309 })
295310 }
296311 }
@@ -366,71 +381,6 @@ type failedJob struct {
366381 errMsg string
367382}
368383
369- func runParallelInstallWithProgress (pkgs []string , progress * ui.StickyProgress ) []failedJob {
370- if len (pkgs ) == 0 {
371- return nil
372- }
373-
374- jobs := make ([]installJob , 0 , len (pkgs ))
375- for _ , pkg := range pkgs {
376- jobs = append (jobs , installJob {name : pkg , isCask : false })
377- }
378-
379- jobChan := make (chan installJob , len (jobs ))
380- results := make (chan installResult , len (jobs ))
381-
382- var wg sync.WaitGroup
383- workers := maxWorkers
384- if len (jobs ) < workers {
385- workers = len (jobs )
386- }
387-
388- for i := 0 ; i < workers ; i ++ {
389- wg .Add (1 )
390- go func () {
391- defer wg .Done ()
392- for job := range jobChan {
393- progress .SetCurrent (job .name )
394- start := time .Now ()
395- errMsg := installFormulaWithError (job .name )
396- elapsed := time .Since (start )
397- progress .IncrementWithStatus (errMsg == "" )
398- duration := ui .FormatDuration (elapsed )
399- if errMsg == "" {
400- progress .PrintLine (" %s %s" , ui .Green ("✔ " + job .name ), ui .Cyan ("(" + duration + ")" ))
401- } else {
402- progress .PrintLine (" %s %s" , ui .Red ("✗ " + job .name + " (" + errMsg + ")" ), ui .Cyan ("(" + duration + ")" ))
403- }
404- results <- installResult {name : job .name , failed : errMsg != "" , isCask : job .isCask , errMsg : errMsg }
405- }
406- }()
407- }
408-
409- go func () {
410- for _ , job := range jobs {
411- jobChan <- job
412- }
413- close (jobChan )
414- }()
415-
416- go func () {
417- wg .Wait ()
418- close (results )
419- }()
420-
421- var failed []failedJob
422- for result := range results {
423- if result .failed {
424- failed = append (failed , failedJob {
425- installJob : installJob {name : result .name , isCask : result .isCask },
426- errMsg : result .errMsg ,
427- })
428- }
429- }
430-
431- return failed
432- }
433-
434384func installCaskWithProgress (pkg string , progress * ui.StickyProgress ) string {
435385 progress .PauseForInteractive ()
436386
@@ -857,3 +807,26 @@ func PreInstallChecks(packageCount int) error {
857807
858808 return nil
859809}
810+
811+ // ResolveFormulaName resolves a formula alias to its canonical name.
812+ // This handles cases like "postgresql" → "postgresql@18" or "kubectl" → "kubernetes-cli".
813+ // Returns the original name if resolution fails.
814+ func ResolveFormulaName (name string ) string {
815+ cmd := exec .Command ("brew" , "info" , "--json" , name )
816+ output , err := cmd .Output ()
817+ if err != nil {
818+ return name
819+ }
820+
821+ var result []struct {
822+ Name string `json:"name"`
823+ }
824+ if err := json .Unmarshal (output , & result ); err != nil {
825+ return name
826+ }
827+
828+ if len (result ) > 0 && result [0 ].Name != "" {
829+ return result [0 ].Name
830+ }
831+ return name
832+ }
0 commit comments