feat: initial commit
This commit is contained in:
259
pkg/wails3kit/updates/providers/githubreleases/provider.go
Normal file
259
pkg/wails3kit/updates/providers/githubreleases/provider.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package githubreleases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAssetTemplate = "{{ .ProductID }}_{{ .Version }}_{{ .OS }}_{{ .Arch }}.zip"
|
||||
defaultChecksumAssetTemplate = "{{ .AssetName }}.sha256"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Owner string
|
||||
Repo string
|
||||
HTTPClient *http.Client
|
||||
PrepareRequest func(*http.Request) error
|
||||
AssetNameTemplate string
|
||||
ChecksumAssetNameTemplate string
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
config Config
|
||||
client *http.Client
|
||||
assetNameTemplate *template.Template
|
||||
checksumNameTemplate *template.Template
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func New(cfg Config) (*Provider, error) {
|
||||
if cfg.Owner == "" || cfg.Repo == "" {
|
||||
return nil, updates.ErrInvalidConfig
|
||||
}
|
||||
client := cfg.HTTPClient
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
assetTemplate := cfg.AssetNameTemplate
|
||||
if assetTemplate == "" {
|
||||
assetTemplate = defaultAssetTemplate
|
||||
}
|
||||
checksumTemplate := cfg.ChecksumAssetNameTemplate
|
||||
if checksumTemplate == "" {
|
||||
checksumTemplate = defaultChecksumAssetTemplate
|
||||
}
|
||||
|
||||
assetNameTemplate, err := template.New("asset").Parse(assetTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checksumNameTemplate, err := template.New("checksum").Parse(checksumTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
config: cfg,
|
||||
client: client,
|
||||
assetNameTemplate: assetNameTemplate,
|
||||
checksumNameTemplate: checksumNameTemplate,
|
||||
baseURL: "https://api.github.com",
|
||||
}, nil
|
||||
}
|
||||
|
||||
type githubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Body string `json:"body"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
Assets []githubAsset `json:"assets"`
|
||||
}
|
||||
|
||||
type githubAsset struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
func (provider *Provider) Resolve(ctx context.Context, req updates.ResolveRequest) (*updates.Release, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, provider.releaseURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if provider.config.PrepareRequest != nil {
|
||||
if err := provider.config.PrepareRequest(request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
response, err := provider.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("unexpected github status: %s", response.Status)
|
||||
}
|
||||
|
||||
var releases []githubRelease
|
||||
if err := json.NewDecoder(response.Body).Decode(&releases); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var best *updates.Release
|
||||
for _, release := range releases {
|
||||
version := strings.TrimPrefix(release.TagName, "v")
|
||||
channel := mapChannel(release)
|
||||
if channel != req.App.Channel {
|
||||
continue
|
||||
}
|
||||
comparison, err := updates.CompareVersions(version, req.App.CurrentVersion)
|
||||
if err != nil || comparison <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
assetName, err := provider.renderAssetName(req.App, version, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checksumName, err := provider.renderChecksumName(req.App, version, assetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
artifactURL := ""
|
||||
checksumURL := ""
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == assetName {
|
||||
artifactURL = asset.BrowserDownloadURL
|
||||
}
|
||||
if asset.Name == checksumName {
|
||||
checksumURL = asset.BrowserDownloadURL
|
||||
}
|
||||
}
|
||||
if artifactURL == "" || checksumURL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
sha256Value, err := provider.readChecksum(ctx, checksumURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
candidate := &updates.Release{
|
||||
ID: version,
|
||||
Version: version,
|
||||
Channel: channel,
|
||||
NotesMarkdown: release.Body,
|
||||
PublishedAt: release.PublishedAt,
|
||||
Artifact: updates.Artifact{
|
||||
Kind: updates.ArtifactKindBundleArchive,
|
||||
Format: updates.ArtifactFormatZip,
|
||||
URL: artifactURL,
|
||||
SHA256: sha256Value,
|
||||
},
|
||||
}
|
||||
if best == nil {
|
||||
best = candidate
|
||||
continue
|
||||
}
|
||||
better, err := updates.CompareVersions(candidate.Version, best.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if better > 0 {
|
||||
best = candidate
|
||||
}
|
||||
}
|
||||
|
||||
return best, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) OpenArtifact(ctx context.Context, release updates.Release) (io.ReadCloser, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, release.Artifact.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if provider.config.PrepareRequest != nil {
|
||||
if err := provider.config.PrepareRequest(request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
response, err := provider.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
defer response.Body.Close()
|
||||
return nil, fmt.Errorf("unexpected artifact status: %s", response.Status)
|
||||
}
|
||||
return response.Body, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) readChecksum(ctx context.Context, url string) (string, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if provider.config.PrepareRequest != nil {
|
||||
if err := provider.config.PrepareRequest(request); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
response, err := provider.client.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("unexpected checksum status: %s", response.Status)
|
||||
}
|
||||
bytes, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Fields(string(bytes))[0], nil
|
||||
}
|
||||
|
||||
func (provider *Provider) releaseURL() string {
|
||||
return fmt.Sprintf("%s/repos/%s/%s/releases", provider.baseURL, provider.config.Owner, provider.config.Repo)
|
||||
}
|
||||
|
||||
func (provider *Provider) renderAssetName(app updates.AppDescriptor, version string, assetName string) (string, error) {
|
||||
return provider.executeTemplate(provider.assetNameTemplate, app, version, assetName)
|
||||
}
|
||||
|
||||
func (provider *Provider) renderChecksumName(app updates.AppDescriptor, version string, assetName string) (string, error) {
|
||||
return provider.executeTemplate(provider.checksumNameTemplate, app, version, assetName)
|
||||
}
|
||||
|
||||
func (provider *Provider) executeTemplate(templateValue *template.Template, app updates.AppDescriptor, version string, assetName string) (string, error) {
|
||||
var builder strings.Builder
|
||||
err := templateValue.Execute(&builder, map[string]string{
|
||||
"ProductID": app.ProductID,
|
||||
"Version": version,
|
||||
"OS": app.OS,
|
||||
"Arch": app.Arch,
|
||||
"AssetName": assetName,
|
||||
})
|
||||
return builder.String(), err
|
||||
}
|
||||
|
||||
func mapChannel(release githubRelease) updates.Channel {
|
||||
if !release.Prerelease {
|
||||
return updates.ChannelStable
|
||||
}
|
||||
if strings.Contains(strings.ToLower(release.TagName), "alpha") {
|
||||
return updates.ChannelAlpha
|
||||
}
|
||||
return updates.ChannelBeta
|
||||
}
|
||||
Reference in New Issue
Block a user