Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 43 additions & 29 deletions pkg/update/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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"
Expand All @@ -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"
}
}
Expand Down
113 changes: 113 additions & 0 deletions pkg/update/check_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}