github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/internal/update/update.go (about) 1 package update 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/ungtb10d/cli/v2/api" 13 "github.com/ungtb10d/cli/v2/internal/ghinstance" 14 "github.com/hashicorp/go-version" 15 "gopkg.in/yaml.v3" 16 ) 17 18 var gitDescribeSuffixRE = regexp.MustCompile(`\d+-\d+-g[a-f0-9]{8}$`) 19 20 // ReleaseInfo stores information about a release 21 type ReleaseInfo struct { 22 Version string `json:"tag_name"` 23 URL string `json:"html_url"` 24 PublishedAt time.Time `json:"published_at"` 25 } 26 27 type StateEntry struct { 28 CheckedForUpdateAt time.Time `yaml:"checked_for_update_at"` 29 LatestRelease ReleaseInfo `yaml:"latest_release"` 30 } 31 32 // CheckForUpdate checks whether this software has had a newer release on GitHub 33 func CheckForUpdate(client *api.Client, stateFilePath, repo, currentVersion string) (*ReleaseInfo, error) { 34 stateEntry, _ := getStateEntry(stateFilePath) 35 if stateEntry != nil && time.Since(stateEntry.CheckedForUpdateAt).Hours() < 24 { 36 return nil, nil 37 } 38 39 releaseInfo, err := getLatestReleaseInfo(client, repo) 40 if err != nil { 41 return nil, err 42 } 43 44 err = setStateEntry(stateFilePath, time.Now(), *releaseInfo) 45 if err != nil { 46 return nil, err 47 } 48 49 if versionGreaterThan(releaseInfo.Version, currentVersion) { 50 return releaseInfo, nil 51 } 52 53 return nil, nil 54 } 55 56 func getLatestReleaseInfo(client *api.Client, repo string) (*ReleaseInfo, error) { 57 var latestRelease ReleaseInfo 58 err := client.REST(ghinstance.Default(), "GET", fmt.Sprintf("repos/%s/releases/latest", repo), nil, &latestRelease) 59 if err != nil { 60 return nil, err 61 } 62 63 return &latestRelease, nil 64 } 65 66 func getStateEntry(stateFilePath string) (*StateEntry, error) { 67 content, err := os.ReadFile(stateFilePath) 68 if err != nil { 69 return nil, err 70 } 71 72 var stateEntry StateEntry 73 err = yaml.Unmarshal(content, &stateEntry) 74 if err != nil { 75 return nil, err 76 } 77 78 return &stateEntry, nil 79 } 80 81 func setStateEntry(stateFilePath string, t time.Time, r ReleaseInfo) error { 82 data := StateEntry{CheckedForUpdateAt: t, LatestRelease: r} 83 content, err := yaml.Marshal(data) 84 if err != nil { 85 return err 86 } 87 88 err = os.MkdirAll(filepath.Dir(stateFilePath), 0755) 89 if err != nil { 90 return err 91 } 92 93 err = os.WriteFile(stateFilePath, content, 0600) 94 return err 95 } 96 97 func versionGreaterThan(v, w string) bool { 98 w = gitDescribeSuffixRE.ReplaceAllStringFunc(w, func(m string) string { 99 idx := strings.IndexRune(m, '-') 100 n, _ := strconv.Atoi(m[0:idx]) 101 return fmt.Sprintf("%d-pre.0", n+1) 102 }) 103 104 vv, ve := version.NewVersion(v) 105 vw, we := version.NewVersion(w) 106 107 return ve == nil && we == nil && vv.GreaterThan(vw) 108 }