diff --git a/pkg/update/check.go b/pkg/update/check.go index 5d32f24a..e0b4b090 100644 --- a/pkg/update/check.go +++ b/pkg/update/check.go @@ -290,6 +290,45 @@ const ( InstallMethodUnknown InstallMethod = "unknown" ) +type installRule struct { + check func(string) bool + envKeys []string + method InstallMethod +} + +func normalizePath(p string) string { return strings.ToLower(filepath.ToSlash(p)) } + +func pathMatchesHomebrew(p string) bool { + p = normalizePath(p) + return strings.Contains(p, "homebrew") || strings.Contains(p, "/cellar/") +} + +func pathMatchesBun(p string) bool { + return strings.Contains(normalizePath(p), "/.bun/") +} + +func pathMatchesPNPM(p string) bool { + p = normalizePath(p) + return strings.Contains(p, "/pnpm/") || strings.Contains(p, "/.pnpm/") +} + +func pathMatchesNPM(p string) bool { + p = normalizePath(p) + return strings.Contains(p, "/npm/") || + strings.Contains(p, "/node_modules/.bin/") || + strings.Contains(p, "/.npm-global/") || + strings.Contains(p, "/.npm/") +} + +func installMethodRules() []installRule { + return []installRule{ + {pathMatchesHomebrew, nil, InstallMethodBrew}, + {pathMatchesBun, []string{"BUN_INSTALL"}, InstallMethodBun}, + {pathMatchesPNPM, []string{"PNPM_HOME"}, InstallMethodPNPM}, + {pathMatchesNPM, []string{"NPM_CONFIG_PREFIX", "npm_config_prefix", "VOLTA_HOME"}, InstallMethodNPM}, + } +} + // DetectInstallMethod detects how kernel was installed and returns the method // along with the path to the kernel binary. func DetectInstallMethod() (InstallMethod, string) { @@ -311,34 +350,7 @@ func DetectInstallMethod() (InstallMethod, string) { } } - // Helpers - norm := func(p string) string { return strings.ToLower(filepath.ToSlash(p)) } - hasHomebrew := func(p string) bool { - p = norm(p) - return strings.Contains(p, "homebrew") || strings.Contains(p, "/cellar/") - } - hasBun := func(p string) bool { p = norm(p); return strings.Contains(p, "/.bun/") } - hasPNPM := func(p string) bool { - p = norm(p) - return strings.Contains(p, "/pnpm/") || strings.Contains(p, "/.pnpm/") - } - hasNPM := func(p string) bool { - p = norm(p) - return strings.Contains(p, "/npm/") || strings.Contains(p, "/node_modules/.bin/") - } - - type rule struct { - check func(string) bool - envKeys []string - method InstallMethod - } - - rules := []rule{ - {hasHomebrew, nil, InstallMethodBrew}, - {hasBun, []string{"BUN_INSTALL"}, InstallMethodBun}, - {hasPNPM, []string{"PNPM_HOME"}, InstallMethodPNPM}, - {hasNPM, []string{"NPM_CONFIG_PREFIX", "npm_config_prefix", "VOLTA_HOME"}, InstallMethodNPM}, - } + rules := installMethodRules() // Path-based detection first for _, c := range candidates { @@ -374,7 +386,10 @@ func DetectInstallMethod() (InstallMethod, string) { // returns a tailored upgrade command. Falls back to default brew command on unknown. func SuggestUpgradeCommand() string { method, _ := DetectInstallMethod() + return suggestUpgradeCommandForMethod(method) +} +func suggestUpgradeCommandForMethod(method InstallMethod) string { switch method { case InstallMethodBrew: return "brew upgrade kernel/tap/kernel" @@ -385,7 +400,6 @@ func SuggestUpgradeCommand() string { case InstallMethodBun: return "bun add -g @onkernel/cli@latest" default: - // Default suggestion when unknown return "brew upgrade kernel/tap/kernel" } } diff --git a/pkg/update/check_test.go b/pkg/update/check_test.go new file mode 100644 index 00000000..b804e707 --- /dev/null +++ b/pkg/update/check_test.go @@ -0,0 +1,113 @@ +package update + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSuggestUpgradeCommandForMethod(t *testing.T) { + tests := []struct { + method InstallMethod + expected string + }{ + {InstallMethodBrew, "brew upgrade kernel/tap/kernel"}, + {InstallMethodNPM, "npm i -g @onkernel/cli@latest"}, + {InstallMethodPNPM, "pnpm add -g @onkernel/cli@latest"}, + {InstallMethodBun, "bun add -g @onkernel/cli@latest"}, + {InstallMethodUnknown, "brew upgrade kernel/tap/kernel"}, + } + for _, tt := range tests { + t.Run(string(tt.method), func(t *testing.T) { + assert.Equal(t, tt.expected, suggestUpgradeCommandForMethod(tt.method)) + }) + } +} + +func TestPathMatchesNPM(t *testing.T) { + tests := []struct { + path string + expected bool + }{ + {"/home/user/.npm-global/bin/kernel", true}, + {"/home/user/.npm/bin/kernel", true}, + {"/usr/local/lib/node_modules/.bin/kernel", true}, + {"/home/user/.local/share/npm/bin/kernel", true}, + {"/opt/homebrew/bin/kernel", false}, + {"/home/user/.bun/bin/kernel", false}, + {"/home/user/.local/share/pnpm/kernel", false}, + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.expected, pathMatchesNPM(tt.path)) + }) + } +} + +func TestPathMatchesBun(t *testing.T) { + tests := []struct { + path string + expected bool + }{ + {"/home/user/.bun/bin/kernel", true}, + {"/home/user/.npm-global/bin/kernel", false}, + {"/opt/homebrew/bin/kernel", false}, + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.expected, pathMatchesBun(tt.path)) + }) + } +} + +func TestPathMatchesPNPM(t *testing.T) { + tests := []struct { + path string + expected bool + }{ + {"/home/user/.local/share/pnpm/kernel", true}, + {"/home/user/.pnpm/global/kernel", true}, + {"/home/user/.npm-global/bin/kernel", false}, + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.expected, pathMatchesPNPM(tt.path)) + }) + } +} + +func TestPathMatchesHomebrew(t *testing.T) { + tests := []struct { + path string + expected bool + }{ + {"/opt/homebrew/bin/kernel", true}, + {"/usr/local/Cellar/kernel/1.0/bin/kernel", true}, + {"/home/linuxbrew/.linuxbrew/Cellar/kernel/1.0/bin/kernel", true}, + {"/home/user/.npm-global/bin/kernel", false}, + } + for _, tt := range tests { + t.Run(tt.path, func(t *testing.T) { + assert.Equal(t, tt.expected, pathMatchesHomebrew(tt.path)) + }) + } +} + +func TestInstallMethodRulesPathPrecedence(t *testing.T) { + rules := installMethodRules() + + detect := func(path string) InstallMethod { + for _, r := range rules { + if r.check(path) { + return r.method + } + } + return InstallMethodUnknown + } + + assert.Equal(t, InstallMethodNPM, detect("/home/user/.npm-global/bin/kernel")) + assert.Equal(t, InstallMethodBun, detect("/home/user/.bun/bin/kernel")) + assert.Equal(t, InstallMethodBrew, detect("/opt/homebrew/bin/kernel")) + assert.Equal(t, InstallMethodPNPM, detect("/home/user/.local/share/pnpm/kernel")) + assert.Equal(t, InstallMethodUnknown, detect("/usr/local/bin/kernel")) +}