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