1//go:build darwin
2
3package update
4
5import (
6 "bytes"
7 "context"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strings"
12 "time"
13)
14
15// detectInstallMethod determines how Crush was installed on macOS.
16func detectInstallMethod(exePath string) InstallMethod {
17 // Check for Nix installation.
18 if strings.HasPrefix(exePath, "/nix/store/") {
19 return InstallMethodNix
20 }
21
22 // Check for npm global installation.
23 if strings.Contains(exePath, "node_modules") {
24 return InstallMethodNPM
25 }
26
27 // Check for go install (typically in ~/go/bin or $GOPATH/bin).
28 gopath := os.Getenv("GOPATH")
29 if gopath == "" {
30 home, _ := os.UserHomeDir()
31 gopath = filepath.Join(home, "go")
32 }
33 if strings.HasPrefix(exePath, filepath.Join(gopath, "bin")) {
34 return InstallMethodGoInstall
35 }
36
37 // Use brew command for accurate Homebrew detection.
38 if isInstalledViaHomebrew(exePath) {
39 return InstallMethodHomebrew
40 }
41
42 // Fallback to path heuristics for Homebrew if brew command not available.
43 if strings.Contains(exePath, "/Cellar/") ||
44 strings.HasPrefix(exePath, "/opt/homebrew/") ||
45 strings.HasPrefix(exePath, "/usr/local/Homebrew/") {
46 return InstallMethodHomebrew
47 }
48
49 return InstallMethodUnknown
50}
51
52// isInstalledViaHomebrew checks if the executable was installed via Homebrew.
53func isInstalledViaHomebrew(exePath string) bool {
54 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
55 defer cancel()
56
57 // Use brew --prefix to find the Homebrew prefix, then check if our path is under it.
58 cmd := exec.CommandContext(ctx, "brew", "--prefix")
59 var stdout bytes.Buffer
60 cmd.Stdout = &stdout
61 if err := cmd.Run(); err != nil {
62 return false
63 }
64
65 brewPrefix := strings.TrimSpace(stdout.String())
66 if brewPrefix == "" {
67 return false
68 }
69
70 // Check if the executable is under Homebrew's Cellar.
71 cellarPath := filepath.Join(brewPrefix, "Cellar")
72 return strings.HasPrefix(exePath, cellarPath)
73}