diff --git a/Makefile b/Makefile index 3ca45472..b08d9771 100644 --- a/Makefile +++ b/Makefile @@ -110,17 +110,20 @@ ifndef HUB $(error please install hub) endif - if [ ! -d ${cliHomebrewDir} ]; then - @echo "homebrew-dnote not found locally. did you clone it?" - @exit 1 + if [ ! -d ${cliHomebrewDir} ]; then \ + @echo "homebrew-dnote not found locally. did you clone it?"; \ + @exit 1; \ fi @echo "==> releasing cli" @${GOPATH}/src/github.com/dnote/dnote/scripts/release.sh cli $(version) ${cliOutputDir} @echo "===> releading on Homebrew" - @homebrew_sha256=$(shasum -a 256 "${outputDir}/dnote_$(version)_darwin_amd64.tar.gz" | cut -d ' ' -f 1) - @(cd "${cliHomebrewDir}" && ./release.sh "$(version)" "${homebrew_sha256}") + @(cd "${cliHomebrewDir}" && \ + ./release.sh \ + "$(version)" \ + "${shasum -a 256 "${outputDir}/dnote_$(version)_darwin_amd64.tar.gz" | cut -d ' ' -f 1}" \ + ) .PHONY: release-cli release-server: build-server diff --git a/pkg/cli/upgrade/upgrade.go b/pkg/cli/upgrade/upgrade.go index d9b3844c..b44041cd 100644 --- a/pkg/cli/upgrade/upgrade.go +++ b/pkg/cli/upgrade/upgrade.go @@ -21,6 +21,7 @@ package upgrade import ( stdCtx "context" "fmt" + "strings" "time" "github.com/dnote/dnote/pkg/cli/consts" @@ -61,21 +62,43 @@ func touchLastUpgrade(ctx context.DnoteCtx) error { return nil } +func fetchLatestStableTag(gh *github.Client, page int) (string, error) { + params := github.ListOptions{ + Page: page, + } + releases, resp, err := gh.Repositories.ListReleases(stdCtx.Background(), "dnote", "dnote", ¶ms) + if err != nil { + return "", errors.Wrapf(err, "fetching releases page %d", page) + } + + for _, release := range releases { + tag := release.GetTagName() + isStable := !release.GetPrerelease() + + if strings.HasPrefix(tag, "cli-") && isStable { + return tag, nil + } + } + + if page == resp.LastPage { + return "", errors.New("No CLI release was found") + } + + return fetchLatestStableTag(gh, page+1) +} + func checkVersion(ctx context.DnoteCtx) error { log.Infof("current version is %s\n", ctx.Version) // Fetch the latest version gh := github.NewClient(nil) - releases, _, err := gh.Repositories.ListReleases(stdCtx.Background(), "dnote", "cli", nil) + latestTag, err := fetchLatestStableTag(gh, 1) if err != nil { - return errors.Wrap(err, "fetching releases") + return errors.Wrap(err, "fetching the latest stable release") } - latest := releases[0] - // releases are tagged in a form of cli-v1.0.0 - latestVersion := (*latest.TagName)[5:] - + latestVersion := latestTag[5:] log.Infof("latest version is %s\n", latestVersion) if latestVersion == ctx.Version { diff --git a/pkg/cli/upgrade/upgrade_test.go b/pkg/cli/upgrade/upgrade_test.go new file mode 100644 index 00000000..7ab9b5ce --- /dev/null +++ b/pkg/cli/upgrade/upgrade_test.go @@ -0,0 +1,157 @@ +package upgrade + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/google/go-github/github" + "github.com/pkg/errors" +) + +func setupGithubClient(t *testing.T) (*github.Client, *http.ServeMux) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + url, err := url.Parse(server.URL + "/") + if err != nil { + t.Fatal(errors.Wrap(err, "parsing mock server url")) + } + + client := github.NewClient(nil) + client.BaseURL = url + client.UploadURL = url + + return client, mux +} + +func TestFetchLatestStableTag(t *testing.T) { + tagCLI0_1_0 := "cli-v0.1.0" + tagCLI0_1_1 := "cli-v0.1.1" + tagCLI0_1_2Beta := "cli-v0.1.2-beta" + tagCLI0_1_3 := "cli-v0.1.3" + tagServer0_1_0 := "server-v0.1.0" + + prereleaseTrue := true + + testCases := []struct { + releases []*github.RepositoryRelease + expected string + }{ + { + releases: []*github.RepositoryRelease{{TagName: &tagCLI0_1_0}}, + expected: tagCLI0_1_0, + }, + { + releases: []*github.RepositoryRelease{ + {TagName: &tagCLI0_1_1}, + {TagName: &tagServer0_1_0}, + {TagName: &tagCLI0_1_0}, + }, + expected: tagCLI0_1_1, + }, + { + releases: []*github.RepositoryRelease{ + {TagName: &tagServer0_1_0}, + {TagName: &tagCLI0_1_1}, + {TagName: &tagCLI0_1_0}, + }, + expected: tagCLI0_1_1, + }, + { + releases: []*github.RepositoryRelease{ + {TagName: &tagCLI0_1_2Beta, Prerelease: &prereleaseTrue}, + {TagName: &tagServer0_1_0}, + {TagName: &tagCLI0_1_1}, + {TagName: &tagCLI0_1_0}, + }, + expected: tagCLI0_1_1, + }, + { + releases: []*github.RepositoryRelease{ + {TagName: &tagCLI0_1_3}, + {TagName: &tagCLI0_1_2Beta, Prerelease: &prereleaseTrue}, + {TagName: &tagCLI0_1_1}, + {TagName: &tagCLI0_1_0}, + }, + expected: tagCLI0_1_3, + }, + } + + for idx, tc := range testCases { + t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) { + // setup + gh, mux := setupGithubClient(t) + mux.HandleFunc("/repos/dnote/dnote/releases", func(w http.ResponseWriter, r *http.Request) { + if err := json.NewEncoder(w).Encode(tc.releases); err != nil { + t.Fatal(errors.Wrap(err, "responding with mock releases")) + } + }) + + // execute + got, err := fetchLatestStableTag(gh, 0) + if err != nil { + t.Fatal(errors.Wrap(err, "performing")) + } + + // test + assert.Equal(t, got, tc.expected, "result mismatch") + }) + } +} + +func TestFetchLatestStableTag_paginated(t *testing.T) { + tagServer0_1_0 := "server-v0.1.0" + tagCLI0_1_2Beta := "cli-v0.1.2-beta" + tagCLI0_1_1 := "cli-v0.1.1" + prereleaseTrue := true + + // set up + gh, mux := setupGithubClient(t) + path := "/repos/dnote/dnote/releases" + mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + page := r.FormValue("page") + + releasesPage1 := []*github.RepositoryRelease{ + {TagName: &tagServer0_1_0}, + } + releasesPage2 := []*github.RepositoryRelease{ + {TagName: &tagCLI0_1_2Beta, Prerelease: &prereleaseTrue}, + {TagName: &tagCLI0_1_1}, + } + + baseURL := gh.BaseURL.String() + + switch page { + case "", "1": + linkHeader := fmt.Sprintf("<%s%s?page=2>; rel=\"next\" <%s%s?page=2>; rel=\"last\"", baseURL, path, baseURL, path) + w.Header().Set("Link", linkHeader) + + if err := json.NewEncoder(w).Encode(releasesPage1); err != nil { + t.Fatal(errors.Wrap(err, "responding with mock releases")) + } + case "2": + linkHeader := fmt.Sprintf("<%s%s?page=1>; rel=\"prev\" <%s%s?page=1>; rel=\"first\"", baseURL, path, baseURL, path) + w.Header().Set("Link", linkHeader) + + if err := json.NewEncoder(w).Encode(releasesPage2); err != nil { + t.Fatal(errors.Wrap(err, "responding with mock releases")) + } + default: + t.Fatal("Should have stopped walking") + } + }) + + // execute + got, err := fetchLatestStableTag(gh, 0) + if err != nil { + t.Fatal(errors.Wrap(err, "performing")) + } + + // test + assert.Equal(t, got, tagCLI0_1_1, "result mismatch") +}