Detailed changes
@@ -5,6 +5,7 @@ go 1.25.0
require (
github.com/JohannesKaufmann/html-to-markdown v1.6.0
github.com/MakeNowJust/heredoc v1.0.0
+ github.com/Masterminds/semver/v3 v3.4.0
github.com/PuerkitoBio/goquery v1.10.3
github.com/alecthomas/chroma/v2 v2.20.0
github.com/anthropics/anthropic-sdk-go v1.13.0
@@ -18,6 +18,8 @@ github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5
github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
@@ -1,51 +0,0 @@
-package cmd
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/charmbracelet/crush/internal/format"
- "github.com/charmbracelet/crush/internal/update"
- "github.com/spf13/cobra"
-)
-
-func init() {
- rootCmd.AddCommand(updateCmd)
-}
-
-var updateCmd = &cobra.Command{
- Use: "check-update",
- Short: "Check for updates",
- Long: `Check if a new version of crush is available.`,
- Example: `
-# Check for updates
-crush check-update
- `,
- RunE: func(cmd *cobra.Command, args []string) error {
- ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Second)
- defer cancel()
-
- sp := format.NewSpinner(ctx, cancel, "Checking for updates")
- sp.Start()
- defer sp.Stop()
-
- info, err := update.CheckForUpdate(ctx)
- if err != nil {
- return fmt.Errorf("failed to check for updates: %w", err)
- }
- sp.Stop()
-
- if !info.Available {
- fmt.Printf("You are running the latest version: %s\n", info.CurrentVersion)
- return nil
- }
-
- fmt.Printf("\nš A new version of crush is available!\n\n")
- fmt.Printf("Current version: %s\n", info.CurrentVersion)
- fmt.Printf("Latest version: %s\n\n", info.LatestVersion)
- fmt.Printf("Visit %s to download the latest version.\n", info.ReleaseURL)
-
- return nil
- },
-}
@@ -287,11 +287,11 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Update Available
case pubsub.UpdateAvailableMsg:
// Show update notification in status bar
- statusMsg := fmt.Sprintf("š Update available! v%s ā v%s. Run 'crush check-update' for details.", msg.CurrentVersion, msg.LatestVersion)
+ statusMsg := fmt.Sprintf("š New Crush version available: v%s ā v%s.", msg.CurrentVersion, msg.LatestVersion)
s, statusCmd := a.status.Update(util.InfoMsg{
Type: util.InfoTypeInfo,
Msg: statusMsg,
- TTL: 10 * time.Second,
+ TTL: 30 * time.Second,
})
a.status = s.(status.StatusCmp)
return a, statusCmd
@@ -34,6 +34,7 @@ type InfoType int
const (
InfoTypeInfo InfoType = iota
+ InfoTypeSuccess
InfoTypeWarn
InfoTypeError
)
@@ -10,6 +10,7 @@ import (
"strings"
"time"
+ "github.com/Masterminds/semver/v3"
"github.com/charmbracelet/crush/internal/version"
)
@@ -38,8 +39,9 @@ func CheckForUpdate(ctx context.Context) (*UpdateInfo, error) {
CurrentVersion: version.Version,
}
- // Skip update check for development versions.
- if strings.Contains(version.Version, "unknown") {
+ cv, err := semver.NewVersion(version.Version)
+ if err != nil {
+ // its devel, unknown, etc
return info, nil
}
@@ -48,13 +50,14 @@ func CheckForUpdate(ctx context.Context) (*UpdateInfo, error) {
return nil, fmt.Errorf("failed to fetch latest release: %w", err)
}
+ lv, err := semver.NewVersion(release.TagName)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse latest version: %w", err)
+ }
+
info.LatestVersion = strings.TrimPrefix(release.TagName, "v")
info.ReleaseURL = release.HTMLURL
-
- // Compare versions.
- if compareVersions(info.CurrentVersion, info.LatestVersion) < 0 {
- info.Available = true
- }
+ info.Available = lv.GreaterThan(cv)
return info, nil
}
@@ -91,40 +94,6 @@ func fetchLatestRelease(ctx context.Context) (*Release, error) {
return &release, nil
}
-// compareVersions compares two semantic versions.
-// Returns -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2.
-func compareVersions(v1, v2 string) int {
- // Remove 'v' prefix if present.
- v1 = strings.TrimPrefix(v1, "v")
- v2 = strings.TrimPrefix(v2, "v")
-
- // Split versions into parts.
- parts1 := strings.Split(v1, ".")
- parts2 := strings.Split(v2, ".")
-
- // Compare each part.
- for i := 0; i < len(parts1) && i < len(parts2); i++ {
- var n1, n2 int
- fmt.Sscanf(parts1[i], "%d", &n1)
- fmt.Sscanf(parts2[i], "%d", &n2)
-
- if n1 < n2 {
- return -1
- } else if n1 > n2 {
- return 1
- }
- }
-
- // If all parts are equal, compare lengths.
- if len(parts1) < len(parts2) {
- return -1
- } else if len(parts1) > len(parts2) {
- return 1
- }
-
- return 0
-}
-
// CheckForUpdateAsync performs an update check in the background and returns immediately.
// If an update is available, it returns the update info through the channel.
func CheckForUpdateAsync(ctx context.Context, dataDir string) <-chan *UpdateInfo {