Parcourir la source

Merge pull request #307 from Mystically11/add-prereleases-to-releases

feat: Add option to include GitHub repository pre-releases in releases widget
Svilen Markov il y a 6 mois
Parent
commit
ec9d455d18
2 fichiers modifiés avec 113 ajouts et 70 suppressions
  1. 11 0
      docs/configuration.md
  2. 102 70
      internal/glance/widget-releases.go

+ 11 - 0
docs/configuration.md

@@ -1332,6 +1332,17 @@ repositories:
   - dockerhub:nginx:stable-alpine
   - dockerhub:nginx:stable-alpine
 ```
 ```
 
 
+To include prereleases you can specify the repository as an object and use the `include-prereleases` property:
+
+**Note: This feature is currently only available for GitHub repositories.**
+
+```yaml
+repositories:
+  - gitlab:inkscape/inkscape
+  - repository: glanceapp/glance
+    include-prereleases: true
+  - codeberg:redict/redict
+```
 
 
 ##### `show-source-icon`
 ##### `show-source-icon`
 Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
 Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.

+ 102 - 70
internal/glance/widget-releases.go

@@ -11,20 +11,21 @@ import (
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"time"
 	"time"
+
+	"gopkg.in/yaml.v3"
 )
 )
 
 
 var releasesWidgetTemplate = mustParseTemplate("releases.html", "widget-base.html")
 var releasesWidgetTemplate = mustParseTemplate("releases.html", "widget-base.html")
 
 
 type releasesWidget struct {
 type releasesWidget struct {
-	widgetBase      `yaml:",inline"`
-	Releases        appReleaseList    `yaml:"-"`
-	releaseRequests []*releaseRequest `yaml:"-"`
-	Repositories    []string          `yaml:"repositories"`
-	Token           string            `yaml:"token"`
-	GitLabToken     string            `yaml:"gitlab-token"`
-	Limit           int               `yaml:"limit"`
-	CollapseAfter   int               `yaml:"collapse-after"`
-	ShowSourceIcon  bool              `yaml:"show-source-icon"`
+	widgetBase     `yaml:",inline"`
+	Releases       appReleaseList    `yaml:"-"`
+	Repositories   []*releaseRequest `yaml:"repositories"`
+	Token          string            `yaml:"token"`
+	GitLabToken    string            `yaml:"gitlab-token"`
+	Limit          int               `yaml:"limit"`
+	CollapseAfter  int               `yaml:"collapse-after"`
+	ShowSourceIcon bool              `yaml:"show-source-icon"`
 }
 }
 
 
 func (widget *releasesWidget) initialize() error {
 func (widget *releasesWidget) initialize() error {
@@ -38,51 +39,21 @@ func (widget *releasesWidget) initialize() error {
 		widget.CollapseAfter = 5
 		widget.CollapseAfter = 5
 	}
 	}
 
 
-	for _, repository := range widget.Repositories {
-		parts := strings.SplitN(repository, ":", 2)
-		var request *releaseRequest
-		if len(parts) == 1 {
-			request = &releaseRequest{
-				source:     releaseSourceGithub,
-				repository: repository,
-			}
-
-			if widget.Token != "" {
-				request.token = &widget.Token
-			}
-		} else if len(parts) == 2 {
-			if parts[0] == string(releaseSourceGitlab) {
-				request = &releaseRequest{
-					source:     releaseSourceGitlab,
-					repository: parts[1],
-				}
-
-				if widget.GitLabToken != "" {
-					request.token = &widget.GitLabToken
-				}
-			} else if parts[0] == string(releaseSourceDockerHub) {
-				request = &releaseRequest{
-					source:     releaseSourceDockerHub,
-					repository: parts[1],
-				}
-			} else if parts[0] == string(releaseSourceCodeberg) {
-				request = &releaseRequest{
-					source:     releaseSourceCodeberg,
-					repository: parts[1],
-				}
-			} else {
-				return errors.New("invalid repository source " + parts[0])
-			}
-		}
+	for i := range widget.Repositories {
+		r := widget.Repositories[i]
 
 
-		widget.releaseRequests = append(widget.releaseRequests, request)
+		if r.source == releaseSourceGithub && widget.Token != "" {
+			r.token = &widget.Token
+		} else if r.source == releaseSourceGitlab && widget.GitLabToken != "" {
+			r.token = &widget.GitLabToken
+		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
 func (widget *releasesWidget) update(ctx context.Context) {
 func (widget *releasesWidget) update(ctx context.Context) {
-	releases, err := fetchLatestReleases(widget.releaseRequests)
+	releases, err := fetchLatestReleases(widget.Repositories)
 
 
 	if !widget.canContinueUpdateAfterHandlingErr(err) {
 	if !widget.canContinueUpdateAfterHandlingErr(err) {
 		return
 		return
@@ -133,9 +104,53 @@ func (r appReleaseList) sortByNewest() appReleaseList {
 }
 }
 
 
 type releaseRequest struct {
 type releaseRequest struct {
-	source     releaseSource
-	repository string
-	token      *string
+	IncludePreleases bool   `yaml:"include-prereleases"`
+	Repository       string `yaml:"repository"`
+
+	source releaseSource
+	token  *string
+}
+
+func (r *releaseRequest) UnmarshalYAML(node *yaml.Node) error {
+	type releaseRequestAlias releaseRequest
+	alias := (*releaseRequestAlias)(r)
+	var repository string
+
+	if err := node.Decode(&repository); err != nil {
+		if err := node.Decode(alias); err != nil {
+			return fmt.Errorf("could not umarshal repository into string or struct: %v", err)
+		}
+	}
+
+	if r.Repository == "" {
+		if repository == "" {
+			return errors.New("repository is required")
+		} else {
+			r.Repository = repository
+		}
+	}
+
+	parts := strings.SplitN(repository, ":", 2)
+	if len(parts) == 1 {
+		r.source = releaseSourceGithub
+	} else if len(parts) == 2 {
+		r.Repository = parts[1]
+
+		switch parts[0] {
+		case string(releaseSourceGithub):
+			r.source = releaseSourceGithub
+		case string(releaseSourceGitlab):
+			r.source = releaseSourceGitlab
+		case string(releaseSourceDockerHub):
+			r.source = releaseSourceDockerHub
+		case string(releaseSourceCodeberg):
+			r.source = releaseSourceCodeberg
+		default:
+			return errors.New("invalid source")
+		}
+	}
+
+	return nil
 }
 }
 
 
 func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
 func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
@@ -152,7 +167,7 @@ func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
 	for i := range results {
 	for i := range results {
 		if errs[i] != nil {
 		if errs[i] != nil {
 			failed++
 			failed++
-			slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].repository, "error", errs[i])
+			slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].Repository, "error", errs[i])
 			continue
 			continue
 		}
 		}
 
 
@@ -187,7 +202,7 @@ func fetchLatestReleaseTask(request *releaseRequest) (*appRelease, error) {
 	return nil, errors.New("unsupported source")
 	return nil, errors.New("unsupported source")
 }
 }
 
 
-type githubReleaseLatestResponseJson struct {
+type githubReleaseResponseJson struct {
 	TagName     string `json:"tag_name"`
 	TagName     string `json:"tag_name"`
 	PublishedAt string `json:"published_at"`
 	PublishedAt string `json:"published_at"`
 	HtmlUrl     string `json:"html_url"`
 	HtmlUrl     string `json:"html_url"`
@@ -197,12 +212,16 @@ type githubReleaseLatestResponseJson struct {
 }
 }
 
 
 func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
 func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
-	httpRequest, err := http.NewRequest(
-		"GET",
-		fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.repository),
-		nil,
-	)
+	var requestURL string
+	if !request.IncludePreleases {
+		requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.Repository)
+	} else {
+		requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases", request.Repository)
+	}
 
 
+	fmt.Println(requestURL)
+
+	httpRequest, err := http.NewRequest("GET", requestURL, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -211,14 +230,29 @@ func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
 		httpRequest.Header.Add("Authorization", "Bearer "+(*request.token))
 		httpRequest.Header.Add("Authorization", "Bearer "+(*request.token))
 	}
 	}
 
 
-	response, err := decodeJsonFromRequest[githubReleaseLatestResponseJson](defaultHTTPClient, httpRequest)
-	if err != nil {
-		return nil, err
+	var response githubReleaseResponseJson
+
+	if !request.IncludePreleases {
+		response, err = decodeJsonFromRequest[githubReleaseResponseJson](defaultHTTPClient, httpRequest)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		responses, err := decodeJsonFromRequest[[]githubReleaseResponseJson](defaultHTTPClient, httpRequest)
+		if err != nil {
+			return nil, err
+		}
+
+		if len(responses) == 0 {
+			return nil, fmt.Errorf("no releases found for repository %s", request.Repository)
+		}
+
+		response = responses[0]
 	}
 	}
 
 
 	return &appRelease{
 	return &appRelease{
 		Source:       releaseSourceGithub,
 		Source:       releaseSourceGithub,
-		Name:         request.repository,
+		Name:         request.Repository,
 		Version:      normalizeVersionFormat(response.TagName),
 		Version:      normalizeVersionFormat(response.TagName),
 		NotesUrl:     response.HtmlUrl,
 		NotesUrl:     response.HtmlUrl,
 		TimeReleased: parseRFC3339Time(response.PublishedAt),
 		TimeReleased: parseRFC3339Time(response.PublishedAt),
@@ -241,17 +275,15 @@ const dockerHubTagsURLFormat = "https://hub.docker.com/v2/namespaces/%s/reposito
 const dockerHubSpecificTagURLFormat = "https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags/%s"
 const dockerHubSpecificTagURLFormat = "https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags/%s"
 
 
 func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
 func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
-
-	nameParts := strings.Split(request.repository, "/")
+	nameParts := strings.Split(request.Repository, "/")
 
 
 	if len(nameParts) > 2 {
 	if len(nameParts) > 2 {
-		return nil, fmt.Errorf("invalid repository name: %s", request.repository)
+		return nil, fmt.Errorf("invalid repository name: %s", request.Repository)
 	} else if len(nameParts) == 1 {
 	} else if len(nameParts) == 1 {
 		nameParts = []string{"library", nameParts[0]}
 		nameParts = []string{"library", nameParts[0]}
 	}
 	}
 
 
 	tagParts := strings.SplitN(nameParts[1], ":", 2)
 	tagParts := strings.SplitN(nameParts[1], ":", 2)
-
 	var requestURL string
 	var requestURL string
 
 
 	if len(tagParts) == 2 {
 	if len(tagParts) == 2 {
@@ -278,7 +310,7 @@ func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
 		}
 		}
 
 
 		if len(response.Results) == 0 {
 		if len(response.Results) == 0 {
-			return nil, fmt.Errorf("no tags found for repository: %s", request.repository)
+			return nil, fmt.Errorf("no tags found for repository: %s", request.Repository)
 		}
 		}
 
 
 		tag = &response.Results[0]
 		tag = &response.Results[0]
@@ -331,7 +363,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
 		"GET",
 		"GET",
 		fmt.Sprintf(
 		fmt.Sprintf(
 			"https://gitlab.com/api/v4/projects/%s/releases/permalink/latest",
 			"https://gitlab.com/api/v4/projects/%s/releases/permalink/latest",
-			url.QueryEscape(request.repository),
+			url.QueryEscape(request.Repository),
 		),
 		),
 		nil,
 		nil,
 	)
 	)
@@ -350,7 +382,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
 
 
 	return &appRelease{
 	return &appRelease{
 		Source:       releaseSourceGitlab,
 		Source:       releaseSourceGitlab,
-		Name:         request.repository,
+		Name:         request.Repository,
 		Version:      normalizeVersionFormat(response.TagName),
 		Version:      normalizeVersionFormat(response.TagName),
 		NotesUrl:     response.Links.Self,
 		NotesUrl:     response.Links.Self,
 		TimeReleased: parseRFC3339Time(response.ReleasedAt),
 		TimeReleased: parseRFC3339Time(response.ReleasedAt),
@@ -368,7 +400,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
 		"GET",
 		"GET",
 		fmt.Sprintf(
 		fmt.Sprintf(
 			"https://codeberg.org/api/v1/repos/%s/releases/latest",
 			"https://codeberg.org/api/v1/repos/%s/releases/latest",
-			request.repository,
+			request.Repository,
 		),
 		),
 		nil,
 		nil,
 	)
 	)
@@ -383,7 +415,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
 
 
 	return &appRelease{
 	return &appRelease{
 		Source:       releaseSourceCodeberg,
 		Source:       releaseSourceCodeberg,
-		Name:         request.repository,
+		Name:         request.Repository,
 		Version:      normalizeVersionFormat(response.TagName),
 		Version:      normalizeVersionFormat(response.TagName),
 		NotesUrl:     response.HtmlUrl,
 		NotesUrl:     response.HtmlUrl,
 		TimeReleased: parseRFC3339Time(response.PublishedAt),
 		TimeReleased: parseRFC3339Time(response.PublishedAt),