Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.21.5
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20260414223304-7a662782a11f
github.com/google/go-github/v74 v74.0.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
Expand Down Expand Up @@ -184,7 +185,6 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/go-github/v74 v74.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
Expand Down
6 changes: 6 additions & 0 deletions pkg/selfupdate/selfupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ func detectRelease(
ctx context.Context,
opts Options,
) (*selfupdate.Release, *selfupdate.Updater, error) {
source, err := newAllPagesSource()
if err != nil {
return nil, nil, fmt.Errorf("initialize release source: %w", err)
}

updater, err := selfupdate.NewUpdater(selfupdate.Config{
Source: source,
Prerelease: opts.IncludePrerelease,
})
if err != nil {
Expand Down
58 changes: 58 additions & 0 deletions pkg/selfupdate/source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package selfupdate

import (
"context"
"fmt"
"os"

"github.com/creativeprojects/go-selfupdate"
"github.com/google/go-github/v74/github"
)

const releasesPerPage = 100

// allPagesSource lists releases across all pages so a target release behind
// more than one page of pre-releases is still found.
type allPagesSource struct {
*selfupdate.GitHubSource
api *github.Client
}

func newAllPagesSource() (*allPagesSource, error) {
base, err := selfupdate.NewGitHubSource(selfupdate.GitHubConfig{})
if err != nil {
return nil, err
}
client := github.NewClient(nil)
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
client = client.WithAuthToken(token)
}
return &allPagesSource{GitHubSource: base, api: client}, nil
}

func (s *allPagesSource) ListReleases(
ctx context.Context,
repository selfupdate.Repository,
) ([]selfupdate.SourceRelease, error) {
owner, repo, err := repository.GetSlug()
if err != nil {
return nil, err
}

opts := &github.ListOptions{PerPage: releasesPerPage}
var releases []selfupdate.SourceRelease
for {
page, resp, err := s.api.Repositories.ListReleases(ctx, owner, repo, opts)
if err != nil {
return nil, fmt.Errorf("list releases for %s/%s: %w", owner, repo, err)
}
for _, rel := range page {
releases = append(releases, selfupdate.NewGitHubRelease(rel))
}
if resp.NextPage == 0 {
break
}
opts.Page = resp.NextPage
}
return releases, nil
}
59 changes: 59 additions & 0 deletions pkg/selfupdate/source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package selfupdate

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/creativeprojects/go-selfupdate"
"github.com/google/go-github/v74/github"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAllPagesSourceListReleasesPaginates(t *testing.T) {
var requestedPages []string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
page := r.URL.Query().Get("page")
requestedPages = append(requestedPages, page)
w.Header().Set("Content-Type", "application/json")

switch page {
case "", "1":
base := "http://" + r.Host + r.URL.Path
w.Header().Set("Link", fmt.Sprintf(`<%s?page=2>; rel="next"`, base))
_, _ = w.Write([]byte(`[{"tag_name":"v2.0.0-beta.1","prerelease":true}]`))
case "2":
_, _ = w.Write([]byte(`[{"tag_name":"v1.0.0","prerelease":false}]`))
default:
_, _ = w.Write([]byte(`[]`))
}
}))
defer srv.Close()

client := github.NewClient(nil)
baseURL, err := url.Parse(srv.URL + "/")
require.NoError(t, err)
client.BaseURL = baseURL

src := &allPagesSource{api: client}

releases, err := src.ListReleases(
context.Background(),
selfupdate.NewRepositorySlug("owner", "repo"),
)
require.NoError(t, err)

require.Len(t, releases, 2, "should aggregate releases from both pages")
assert.Equal(t, "v2.0.0-beta.1", releases[0].GetTagName())
assert.Equal(t, "v1.0.0", releases[1].GetTagName(), "stable release on page 2 must be returned")
assert.Equal(
t,
[]string{"", "2"},
requestedPages,
"should request page 1 then follow next to page 2",
)
}
Loading