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  }