15 githubApiUrl = "https://api.github.com/repos/charmbracelet/crush/releases/latest"
16 userAgent = "crush/1.0"
19// Default is the default [Client].
20var Default Client = &github{}
22// Info contains information about an available update.
29// Matches a version string like:
30// v0.0.0-0.20251231235959-06c807842604
31var goInstallRegexp = regexp.MustCompile(`^v?\d+\.\d+\.\d+-\d+\.\d{14}-[0-9a-f]{12}$`)
33func (i Info) IsDevelopment() bool {
34 return i.Current == "devel" || i.Current == "unknown" || strings.Contains(i.Current, "dirty") || goInstallRegexp.MatchString(i.Current)
37// Available returns true if there's an update available.
39// If both current and latest are stable versions, returns true if versions are
41// If current is a pre-release and latest isn't, returns true.
42// If latest is a pre-release and current isn't, returns false.
43func (i Info) Available() bool {
44 cpr := strings.Contains(i.Current, "-")
45 lpr := strings.Contains(i.Latest, "-")
46 // current is pre release && latest isn't a prerelease
50 // latest is pre release && current isn't a prerelease
54 return i.Current != i.Latest
57// Check checks if a new version is available.
58func Check(ctx context.Context, current string, client Client) (Info, error) {
64 release, err := client.Latest(ctx)
66 return info, fmt.Errorf("failed to fetch latest release: %w", err)
69 info.Latest = strings.TrimPrefix(release.TagName, "v")
70 info.Current = strings.TrimPrefix(info.Current, "v")
71 info.URL = release.HTMLURL
75// Release represents a GitHub release.
77 TagName string `json:"tag_name"`
78 HTMLURL string `json:"html_url"`
81// Client is a client that can get the latest release.
82type Client interface {
83 Latest(ctx context.Context) (*Release, error)
88// Latest implements [Client].
89func (c *github) Latest(ctx context.Context) (*Release, error) {
90 client := &http.Client{
91 Timeout: 30 * time.Second,
94 req, err := http.NewRequestWithContext(ctx, "GET", githubApiUrl, nil)
98 req.Header.Set("User-Agent", userAgent)
99 req.Header.Set("Accept", "application/vnd.github.v3+json")
101 resp, err := client.Do(req)
105 defer resp.Body.Close()
107 if resp.StatusCode != http.StatusOK {
108 body, _ := io.ReadAll(resp.Body)
109 return nil, fmt.Errorf("github api returned status %d: %s", resp.StatusCode, string(body))
113 if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list