Skip to content

Commit 2c5f167

Browse files
committed
refactor: consolidate install detection and simplify upgrade command
- Add "update" as alias for "upgrade" command - Show release URL when new version is available - Normalize version display (strip 'v' prefix from both versions) - Move DetectInstallMethod to pkg/update for reuse by SuggestUpgradeCommand - Remove obsolete old-tap migration logic (taps now mirrored on both orgs)
1 parent eda5487 commit 2c5f167

2 files changed

Lines changed: 66 additions & 165 deletions

File tree

cmd/upgrade.go

Lines changed: 21 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"os/exec"
8-
"path/filepath"
98
"runtime"
109
"strings"
1110
"time"
@@ -15,22 +14,12 @@ import (
1514
"github.com/spf13/cobra"
1615
)
1716

18-
// InstallMethod represents how kernel was installed
19-
type InstallMethod string
20-
21-
const (
22-
InstallMethodBrew InstallMethod = "brew"
23-
InstallMethodPNPM InstallMethod = "pnpm"
24-
InstallMethodNPM InstallMethod = "npm"
25-
InstallMethodBun InstallMethod = "bun"
26-
InstallMethodUnknown InstallMethod = "unknown"
27-
)
28-
2917
var dryRun bool
3018

3119
var upgradeCmd = &cobra.Command{
32-
Use: "upgrade",
33-
Short: "Upgrade the Kernel CLI to the latest version",
20+
Use: "upgrade",
21+
Aliases: []string{"update"},
22+
Short: "Upgrade the Kernel CLI to the latest version",
3423
Long: `Upgrade the Kernel CLI to the latest version.
3524
3625
Supported installation methods:
@@ -56,7 +45,7 @@ func runUpgrade(cmd *cobra.Command, args []string) error {
5645

5746
pterm.Info.Println("Checking for updates...")
5847

59-
latestTag, _, err := update.FetchLatest(ctx)
48+
latestTag, releaseURL, err := update.FetchLatest(ctx)
6049
if err != nil {
6150
return fmt.Errorf("failed to check for updates: %w", err)
6251
}
@@ -68,16 +57,19 @@ func runUpgrade(cmd *cobra.Command, args []string) error {
6857
pterm.Warning.Printf("Could not compare versions (%s vs %s): %v\n", currentVersion, latestTag, err)
6958
pterm.Info.Println("Proceeding with upgrade...")
7059
} else if !isNewer {
71-
pterm.Success.Printf("You are already on the latest version (%s)\n", currentVersion)
60+
pterm.Success.Printf("You are already on the latest version (%s)\n", strings.TrimPrefix(currentVersion, "v"))
7261
return nil
7362
} else {
74-
pterm.Info.Printf("New version available: %s → %s\n", currentVersion, strings.TrimPrefix(latestTag, "v"))
63+
pterm.Info.Printf("New version available: %s → %s\n", strings.TrimPrefix(currentVersion, "v"), strings.TrimPrefix(latestTag, "v"))
64+
if releaseURL != "" {
65+
pterm.Info.Printf("Release notes: %s\n", releaseURL)
66+
}
7567
}
7668

7769
// Detect installation method
78-
method, binaryPath := DetectInstallMethod()
70+
method, binaryPath := update.DetectInstallMethod()
7971

80-
if method == InstallMethodUnknown {
72+
if method == update.InstallMethodUnknown {
8173
printManualUpgradeInstructions(latestTag, binaryPath)
8274
return fmt.Errorf("could not detect installation method")
8375
}
@@ -91,114 +83,34 @@ func runUpgrade(cmd *cobra.Command, args []string) error {
9183
return executeUpgrade(method)
9284
}
9385

94-
// DetectInstallMethod detects how kernel was installed and returns the method
95-
// along with the path to the kernel binary.
96-
func DetectInstallMethod() (InstallMethod, string) {
97-
// Collect candidate paths: current executable and shell-resolved binary
98-
candidates := []string{}
99-
binaryPath := ""
100-
101-
if exe, err := os.Executable(); err == nil && exe != "" {
102-
if real, err2 := filepath.EvalSymlinks(exe); err2 == nil && real != "" {
103-
exe = real
104-
}
105-
candidates = append(candidates, exe)
106-
binaryPath = exe
107-
}
108-
if which, err := exec.LookPath("kernel"); err == nil && which != "" {
109-
candidates = append(candidates, which)
110-
if binaryPath == "" {
111-
binaryPath = which
112-
}
113-
}
114-
115-
// Helpers
116-
norm := func(p string) string { return strings.ToLower(filepath.ToSlash(p)) }
117-
hasHomebrew := func(p string) bool {
118-
p = norm(p)
119-
return strings.Contains(p, "homebrew") || strings.Contains(p, "/cellar/")
120-
}
121-
hasBun := func(p string) bool { p = norm(p); return strings.Contains(p, "/.bun/") }
122-
hasPNPM := func(p string) bool {
123-
p = norm(p)
124-
return strings.Contains(p, "/pnpm/") || strings.Contains(p, "/.pnpm/")
125-
}
126-
hasNPM := func(p string) bool {
127-
p = norm(p)
128-
return strings.Contains(p, "/npm/") || strings.Contains(p, "/node_modules/.bin/")
129-
}
130-
131-
type rule struct {
132-
check func(string) bool
133-
envKeys []string
134-
method InstallMethod
135-
}
136-
137-
rules := []rule{
138-
{hasHomebrew, nil, InstallMethodBrew},
139-
{hasBun, []string{"BUN_INSTALL"}, InstallMethodBun},
140-
{hasPNPM, []string{"PNPM_HOME"}, InstallMethodPNPM},
141-
{hasNPM, []string{"NPM_CONFIG_PREFIX", "npm_config_prefix", "VOLTA_HOME"}, InstallMethodNPM},
142-
}
143-
144-
// Path-based detection first
145-
for _, c := range candidates {
146-
for _, r := range rules {
147-
if r.check != nil && r.check(c) {
148-
return r.method, binaryPath
149-
}
150-
}
151-
}
152-
153-
// Env-only fallbacks
154-
envSet := func(keys []string) bool {
155-
for _, k := range keys {
156-
if k == "" {
157-
continue
158-
}
159-
if os.Getenv(k) != "" {
160-
return true
161-
}
162-
}
163-
return false
164-
}
165-
for _, r := range rules {
166-
if len(r.envKeys) > 0 && envSet(r.envKeys) {
167-
return r.method, binaryPath
168-
}
169-
}
170-
171-
return InstallMethodUnknown, binaryPath
172-
}
173-
17486
// getUpgradeCommand returns the command string for a given installation method
175-
func getUpgradeCommand(method InstallMethod) string {
87+
func getUpgradeCommand(method update.InstallMethod) string {
17688
switch method {
177-
case InstallMethodBrew:
89+
case update.InstallMethodBrew:
17890
return "brew upgrade kernel/tap/kernel"
179-
case InstallMethodPNPM:
91+
case update.InstallMethodPNPM:
18092
return "pnpm add -g @onkernel/cli@latest"
181-
case InstallMethodNPM:
93+
case update.InstallMethodNPM:
18294
return "npm i -g @onkernel/cli@latest"
183-
case InstallMethodBun:
95+
case update.InstallMethodBun:
18496
return "bun add -g @onkernel/cli@latest"
18597
default:
18698
return ""
18799
}
188100
}
189101

190102
// executeUpgrade runs the appropriate upgrade command based on the installation method
191-
func executeUpgrade(method InstallMethod) error {
103+
func executeUpgrade(method update.InstallMethod) error {
192104
var cmd *exec.Cmd
193105

194106
switch method {
195-
case InstallMethodBrew:
107+
case update.InstallMethodBrew:
196108
cmd = exec.Command("brew", "upgrade", "kernel/tap/kernel")
197-
case InstallMethodPNPM:
109+
case update.InstallMethodPNPM:
198110
cmd = exec.Command("pnpm", "add", "-g", "@onkernel/cli@latest")
199-
case InstallMethodNPM:
111+
case update.InstallMethodNPM:
200112
cmd = exec.Command("npm", "i", "-g", "@onkernel/cli@latest")
201-
case InstallMethodBun:
113+
case update.InstallMethodBun:
202114
cmd = exec.Command("bun", "add", "-g", "@onkernel/cli@latest")
203115
default:
204116
return fmt.Errorf("unknown installation method")

pkg/update/check.go

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -234,19 +234,36 @@ func saveCache(path string, c Cache) error {
234234
return os.WriteFile(path, b, 0o600)
235235
}
236236

237-
// SuggestUpgradeCommand attempts to infer how the user installed kernel and
238-
// returns a tailored upgrade command. Falls back to empty string on unknown.
239-
func SuggestUpgradeCommand() string {
237+
// InstallMethod represents how kernel was installed
238+
type InstallMethod string
239+
240+
const (
241+
InstallMethodBrew InstallMethod = "brew"
242+
InstallMethodPNPM InstallMethod = "pnpm"
243+
InstallMethodNPM InstallMethod = "npm"
244+
InstallMethodBun InstallMethod = "bun"
245+
InstallMethodUnknown InstallMethod = "unknown"
246+
)
247+
248+
// DetectInstallMethod detects how kernel was installed and returns the method
249+
// along with the path to the kernel binary.
250+
func DetectInstallMethod() (InstallMethod, string) {
240251
// Collect candidate paths: current executable and shell-resolved binary
241252
candidates := []string{}
253+
binaryPath := ""
254+
242255
if exe, err := os.Executable(); err == nil && exe != "" {
243256
if real, err2 := filepath.EvalSymlinks(exe); err2 == nil && real != "" {
244257
exe = real
245258
}
246259
candidates = append(candidates, exe)
260+
binaryPath = exe
247261
}
248262
if which, err := exec.LookPath("kernel"); err == nil && which != "" {
249263
candidates = append(candidates, which)
264+
if binaryPath == "" {
265+
binaryPath = which
266+
}
250267
}
251268

252269
// Helpers
@@ -268,25 +285,21 @@ func SuggestUpgradeCommand() string {
268285
type rule struct {
269286
check func(string) bool
270287
envKeys []string
271-
cmd string
288+
method InstallMethod
272289
}
273290

274291
rules := []rule{
275-
{hasHomebrew, nil, ""}, // Homebrew handled specially below
276-
{hasBun, []string{"BUN_INSTALL"}, "bun add -g @onkernel/cli@latest"},
277-
{hasPNPM, []string{"PNPM_HOME"}, "pnpm add -g @onkernel/cli@latest"},
278-
{hasNPM, []string{"NPM_CONFIG_PREFIX", "npm_config_prefix", "VOLTA_HOME"}, "npm i -g @onkernel/cli@latest"},
292+
{hasHomebrew, nil, InstallMethodBrew},
293+
{hasBun, []string{"BUN_INSTALL"}, InstallMethodBun},
294+
{hasPNPM, []string{"PNPM_HOME"}, InstallMethodPNPM},
295+
{hasNPM, []string{"NPM_CONFIG_PREFIX", "npm_config_prefix", "VOLTA_HOME"}, InstallMethodNPM},
279296
}
280297

281298
// Path-based detection first
282299
for _, c := range candidates {
283300
for _, r := range rules {
284301
if r.check != nil && r.check(c) {
285-
if r.cmd == "" {
286-
// Homebrew detected, check which tap
287-
return suggestHomebrewCommand(c)
288-
}
289-
return r.cmd
302+
return r.method, binaryPath
290303
}
291304
}
292305
}
@@ -305,55 +318,31 @@ func SuggestUpgradeCommand() string {
305318
}
306319
for _, r := range rules {
307320
if len(r.envKeys) > 0 && envSet(r.envKeys) {
308-
return r.cmd
321+
return r.method, binaryPath
309322
}
310323
}
311324

312-
// Default suggestion when unknown
313-
return "brew upgrade kernel/tap/kernel"
314-
}
315-
316-
// suggestHomebrewCommand returns the appropriate brew command based on which tap
317-
// the user has installed. If they have the old onkernel/tap, they need to uninstall
318-
// and reinstall from the new kernel/tap.
319-
func suggestHomebrewCommand(exePath string) string {
320-
// Check if the executable path indicates the old tap by looking at version.
321-
// The Cellar path format is: /opt/homebrew/Cellar/kernel/<version>/bin/kernel
322-
// Versions before 0.13.0 were published to onkernel/tap, 0.13.0+ to kernel/tap.
323-
if isOldTapVersion(exePath) {
324-
return "brew uninstall kernel && brew install kernel/tap/kernel"
325-
}
326-
327-
return "brew upgrade kernel/tap/kernel"
325+
return InstallMethodUnknown, binaryPath
328326
}
329327

330-
// isOldTapVersion checks if the Homebrew Cellar path contains a version < 0.13.0,
331-
// which indicates it was installed from the old onkernel/tap.
332-
func isOldTapVersion(exePath string) bool {
333-
// Expected path format: .../Cellar/kernel/<version>/...
334-
normPath := strings.ToLower(filepath.ToSlash(exePath))
335-
if !strings.Contains(normPath, "/cellar/kernel/") {
336-
return false
337-
}
338-
339-
// Extract version from path
340-
parts := strings.Split(normPath, "/cellar/kernel/")
341-
if len(parts) < 2 {
342-
return false
343-
}
344-
remainder := parts[1] // e.g., "0.12.4/bin/kernel"
345-
versionPart := strings.Split(remainder, "/")[0]
346-
if versionPart == "" {
347-
return false
348-
}
349-
350-
// Parse and compare versions
351-
installed, err := semver.NewVersion(versionPart)
352-
if err != nil {
353-
return false
328+
// SuggestUpgradeCommand attempts to infer how the user installed kernel and
329+
// returns a tailored upgrade command. Falls back to default brew command on unknown.
330+
func SuggestUpgradeCommand() string {
331+
method, _ := DetectInstallMethod()
332+
333+
switch method {
334+
case InstallMethodBrew:
335+
return "brew upgrade kernel/tap/kernel"
336+
case InstallMethodPNPM:
337+
return "pnpm add -g @onkernel/cli@latest"
338+
case InstallMethodNPM:
339+
return "npm i -g @onkernel/cli@latest"
340+
case InstallMethodBun:
341+
return "bun add -g @onkernel/cli@latest"
342+
default:
343+
// Default suggestion when unknown
344+
return "brew upgrade kernel/tap/kernel"
354345
}
355-
threshold, _ := semver.NewVersion("0.13.0")
356-
return installed.LessThan(threshold)
357346
}
358347

359348
// invokedTrivialCommand returns true if the argv suggests a trivial invocation

0 commit comments

Comments
 (0)