refactor: address code review findings for self-update

- go.mod: 1.25.12 → 1.25 (fix nonexistent patch version)
- Remove init() log mutation, pass logger explicitly via initUpdater()
- Add 10s HTTP timeout via context.WithTimeout on API calls
- Fix version comparison using proper semver (current.LessThan(newVer))
- Extract shared initUpdater() helper to eliminate duplicate creation
- Dev build warning now goes to stderr consistently
- Exit with code 0 after successful interactive update (binary replaced)
This commit is contained in:
2026-08-22 10:08:48 +02:00
parent fed2b44c28
commit 7f2c9b2ba8
2 changed files with 63 additions and 70 deletions
+3 -1
View File
@@ -83,7 +83,9 @@ func main() {
} }
} }
checkForUpdateInteractive(verbose) if checkForUpdateInteractive(verbose) {
os.Exit(0)
}
m := newFullModel() m := newFullModel()
p := tea.NewProgram(m, tea.WithAltScreen()) p := tea.NewProgram(m, tea.WithAltScreen())
+60 -69
View File
@@ -7,48 +7,77 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/creativeprojects/go-selfupdate" "github.com/creativeprojects/go-selfupdate"
) )
const githubSlug = "skoelle/kctl-tui" const (
githubSlug = "skoelle/kctl-tui"
updateTimeout = 10 * time.Second
)
func runUpdate(verbose bool) error { func initUpdater(verbose bool) (*selfupdate.Updater, error) {
if verbose { if verbose {
selfupdate.SetLogger(&verboseLogger{}) selfupdate.SetLogger(&verboseLogger{})
} }
current := version source, err := selfupdate.NewGitHubSource(selfupdate.GitHubConfig{})
if current == "dev" { if err != nil {
return nil, fmt.Errorf("failed to init GitHub source: %w", err)
}
return selfupdate.NewUpdater(selfupdate.Config{
Source: source,
})
}
func runUpdate(verbose bool) error {
if version == "dev" {
fmt.Fprintln(os.Stderr, "WARNING: running dev build — cannot compare versions") fmt.Fprintln(os.Stderr, "WARNING: running dev build — cannot compare versions")
fmt.Println("Skipping version check. Build from a tagged release to enable self-update.") fmt.Fprintln(os.Stderr, "Skipping version check. Build from a tagged release to enable self-update.")
return nil return nil
} }
fmt.Printf("Current version: %s\n", current) updater, err := initUpdater(verbose)
fmt.Println("Checking for updates...") if err != nil {
return err
}
rel, err := detectLatest() ctx, cancel := context.WithTimeout(context.Background(), updateTimeout)
defer cancel()
repo := selfupdate.ParseSlug(githubSlug)
rel, found, err := updater.DetectLatest(ctx, repo)
if err != nil { if err != nil {
return fmt.Errorf("failed to check for updates: %w", err) return fmt.Errorf("failed to check for updates: %w", err)
} }
if !found {
if rel == nil {
fmt.Println("Already up-to-date.") fmt.Println("Already up-to-date.")
return nil return nil
} }
fmt.Printf("Found version %s. Updating...\n", rel.Version()) current, _ := semver.NewVersion(version)
newVersion := rel.Version()
if err := applyUpdate(rel); err != nil { newVer, _ := semver.NewVersion(newVersion)
if current != nil && !current.LessThan(newVer) {
fmt.Println("Already up-to-date.")
return nil
}
fmt.Printf("Current version: %s\n", version)
fmt.Printf("Found version %s. Updating...\n", newVersion)
if err := updater.UpdateTo(ctx, rel, ""); err != nil {
return fmt.Errorf("update failed: %w", err) return fmt.Errorf("update failed: %w", err)
} }
fmt.Printf("Updated from %s to %s\n", current, rel.Version()) fmt.Printf("Updated from %s to %s\n", current, newVersion)
return nil return nil
} }
@@ -59,27 +88,34 @@ func checkForUpdateInteractive(verbose bool) bool {
return false return false
} }
if verbose { updater, err := initUpdater(verbose)
selfupdate.SetLogger(&verboseLogger{})
}
rel, err := detectLatest()
if err != nil { if err != nil {
// Silently ignore network errors — don't block startup
if verbose { if verbose {
fmt.Fprintf(os.Stderr, "Update check failed: %v\n", err) fmt.Fprintf(os.Stderr, "Update check failed: %v\n", err)
} }
return false return false
} }
if rel == nil { ctx, cancel := context.WithTimeout(context.Background(), updateTimeout)
defer cancel()
repo := selfupdate.ParseSlug(githubSlug)
rel, found, err := updater.DetectLatest(ctx, repo)
if err != nil {
if verbose {
fmt.Fprintf(os.Stderr, "Update check failed: %v\n", err)
}
return false
}
if !found {
return false return false
} }
current, _ := semver.NewVersion(version) current, _ := semver.NewVersion(version)
newVersion := rel.Version() newVersion := rel.Version()
newVer, _ := semver.NewVersion(newVersion)
if current != nil && !rel.GreaterThan(current.String()) { if current != nil && !current.LessThan(newVer) {
return false return false
} }
@@ -94,56 +130,15 @@ func checkForUpdateInteractive(verbose bool) bool {
} }
fmt.Println("Updating...") fmt.Println("Updating...")
if err := applyUpdate(rel); err != nil { if err := updater.UpdateTo(ctx, rel, ""); err != nil {
fmt.Fprintf(os.Stderr, "Update failed: %v\n", err) fmt.Fprintf(os.Stderr, "Update failed: %v\n", err)
return false return false
} }
fmt.Printf("Updated to %s. Starting kctl-tui...\n", newVersion) fmt.Printf("Updated to %s. Please restart kctl-tui.\n", newVersion)
return true return true
} }
func detectLatest() (*selfupdate.Release, error) {
source, err := selfupdate.NewGitHubSource(selfupdate.GitHubConfig{})
if err != nil {
return nil, fmt.Errorf("failed to init GitHub source: %w", err)
}
updater, err := selfupdate.NewUpdater(selfupdate.Config{
Source: source,
})
if err != nil {
return nil, fmt.Errorf("failed to create updater: %w", err)
}
repo := selfupdate.ParseSlug(githubSlug)
rel, found, err := updater.DetectLatest(context.Background(), repo)
if err != nil {
return nil, err
}
if !found {
return nil, nil
}
return rel, nil
}
func applyUpdate(rel *selfupdate.Release) error {
source, err := selfupdate.NewGitHubSource(selfupdate.GitHubConfig{})
if err != nil {
return fmt.Errorf("failed to init GitHub source: %w", err)
}
updater, err := selfupdate.NewUpdater(selfupdate.Config{
Source: source,
})
if err != nil {
return fmt.Errorf("failed to create updater: %w", err)
}
return updater.UpdateTo(context.Background(), rel, "")
}
type verboseLogger struct{} type verboseLogger struct{}
func (l *verboseLogger) Print(v ...any) { func (l *verboseLogger) Print(v ...any) {
@@ -153,7 +148,3 @@ func (l *verboseLogger) Print(v ...any) {
func (l *verboseLogger) Printf(format string, v ...any) { func (l *verboseLogger) Printf(format string, v ...any) {
fmt.Fprintf(os.Stderr, format, v...) fmt.Fprintf(os.Stderr, format, v...)
} }
func init() {
log.SetOutput(os.Stderr)
}