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  }