update_darwin.go

 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}