From 4425365004faf125a33de404fbe7593cd206625e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 18 May 2021 19:40:28 +0200 Subject: [PATCH 1/2] Add `release view --json` support --- pkg/cmd/issue/view/view.go | 2 +- pkg/cmd/release/create/create.go | 2 +- pkg/cmd/release/shared/fetch.go | 104 +++++++++++++++++++++++++++---- pkg/cmd/release/view/view.go | 24 +++++-- 4 files changed, 111 insertions(+), 21 deletions(-) diff --git a/pkg/cmd/issue/view/view.go b/pkg/cmd/issue/view/view.go index b6b42ff9f..78be7df09 100644 --- a/pkg/cmd/issue/view/view.go +++ b/pkg/cmd/issue/view/view.go @@ -108,7 +108,7 @@ func viewRun(opts *ViewOptions) error { opts.IO.DetectTerminalTheme() if err := opts.IO.StartPager(); err != nil { - fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v", err) + fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err) } defer opts.IO.StopPager() diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index c80d47953..5c91eed9b 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -313,7 +313,7 @@ func createRun(opts *CreateOptions) error { } } - fmt.Fprintf(opts.IO.Out, "%s\n", newRelease.HTMLURL) + fmt.Fprintf(opts.IO.Out, "%s\n", newRelease.URL) return nil } diff --git a/pkg/cmd/release/shared/fetch.go b/pkg/cmd/release/shared/fetch.go index 4cc31ff48..9dad74f0a 100644 --- a/pkg/cmd/release/shared/fetch.go +++ b/pkg/cmd/release/shared/fetch.go @@ -6,6 +6,8 @@ import ( "fmt" "io/ioutil" "net/http" + "reflect" + "strings" "time" "github.com/cli/cli/api" @@ -13,30 +15,106 @@ import ( "github.com/cli/cli/internal/ghrepo" ) -type Release struct { - TagName string `json:"tag_name"` - Name string `json:"name"` - Body string `json:"body"` - IsDraft bool `json:"draft"` - IsPrerelease bool `json:"prerelease"` - CreatedAt time.Time `json:"created_at"` - PublishedAt time.Time `json:"published_at"` +var ReleaseFields = []string{ + "url", + "apiUrl", + "uploadUrl", + "tarballUrl", + "zipballUrl", + "id", + "tagName", + "name", + "body", + "isDraft", + "isPrerelease", + "createdAt", + "publishedAt", + "targetCommitish", + "author", + "assets", +} - APIURL string `json:"url"` - UploadURL string `json:"upload_url"` - HTMLURL string `json:"html_url"` - Assets []ReleaseAsset +type Release struct { + ID string `json:"node_id"` + TagName string `json:"tag_name"` + Name string `json:"name"` + Body string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt *time.Time `json:"published_at"` + + TargetCommitish string `json:"target_commitish"` + + APIURL string `json:"url"` + UploadURL string `json:"upload_url"` + TarballURL string `json:"tarball_url"` + ZipballURL string `json:"zipball_url"` + URL string `json:"html_url"` + Assets []ReleaseAsset Author struct { - Login string + ID string `json:"node_id"` + Login string `json:"login"` } } type ReleaseAsset struct { + ID string `json:"node_id"` Name string + Label string Size int64 State string APIURL string `json:"url"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DownloadCount int `json:"download_count"` + ContentType string `json:"content_type"` + BrowserDownloadURL string `json:"browser_download_url"` +} + +func (rel *Release) ExportData(fields []string) *map[string]interface{} { + v := reflect.ValueOf(rel).Elem() + fieldByName := func(v reflect.Value, field string) reflect.Value { + return v.FieldByNameFunc(func(s string) bool { + return strings.EqualFold(field, s) + }) + } + data := map[string]interface{}{} + + for _, f := range fields { + switch f { + case "author": + data[f] = map[string]interface{}{ + "id": rel.Author.ID, + "login": rel.Author.Login, + } + case "assets": + assets := make([]interface{}, 0, len(rel.Assets)) + for _, a := range rel.Assets { + assets = append(assets, map[string]interface{}{ + "url": a.BrowserDownloadURL, + "apiUrl": a.APIURL, + "id": a.ID, + "name": a.Name, + "label": a.Label, + "size": a.Size, + "state": a.State, + "createdAt": a.CreatedAt, + "updatedAt": a.UpdatedAt, + "downloadCount": a.DownloadCount, + "contentType": a.ContentType, + }) + } + data[f] = assets + default: + sf := fieldByName(v, f) + data[f] = sf.Interface() + } + } + + return &data } // FetchRelease finds a repository release by its tagName. diff --git a/pkg/cmd/release/view/view.go b/pkg/cmd/release/view/view.go index 9bcccb675..a7b796bee 100644 --- a/pkg/cmd/release/view/view.go +++ b/pkg/cmd/release/view/view.go @@ -26,6 +26,7 @@ type ViewOptions struct { IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) Browser browser + Exporter cmdutil.Exporter TagName string WebMode bool @@ -64,6 +65,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman } cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the release in the browser") + cmdutil.AddJSONFlags(cmd, &opts.Exporter, shared.ReleaseFields) return cmd } @@ -95,9 +97,19 @@ func viewRun(opts *ViewOptions) error { if opts.WebMode { if opts.IO.IsStdoutTTY() { - fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(release.HTMLURL)) + fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(release.URL)) } - return opts.Browser.Browse(release.HTMLURL) + return opts.Browser.Browse(release.URL) + } + + opts.IO.DetectTerminalTheme() + if err := opts.IO.StartPager(); err != nil { + fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err) + } + defer opts.IO.StopPager() + + if opts.Exporter != nil { + return opts.Exporter.Write(opts.IO.Out, release, opts.IO.ColorEnabled()) } if opts.IO.IsStdoutTTY() { @@ -126,10 +138,10 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error { if release.IsDraft { fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s created this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.CreatedAt))))) } else { - fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.PublishedAt))))) + fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(*release.PublishedAt))))) } - style := markdown.GetStyle(io.DetectTerminalTheme()) + style := markdown.GetStyle(io.TerminalTheme()) renderedDescription, err := markdown.Render(release.Body, style) if err != nil { return err @@ -151,7 +163,7 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error { fmt.Fprint(w, "\n") } - fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("View on GitHub: %s", release.HTMLURL))) + fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("View on GitHub: %s", release.URL))) return nil } @@ -165,7 +177,7 @@ func renderReleasePlain(w io.Writer, release *shared.Release) error { if !release.IsDraft { fmt.Fprintf(w, "published:\t%s\n", release.PublishedAt.Format(time.RFC3339)) } - fmt.Fprintf(w, "url:\t%s\n", release.HTMLURL) + fmt.Fprintf(w, "url:\t%s\n", release.URL) for _, a := range release.Assets { fmt.Fprintf(w, "asset:\t%s\n", a.Name) } From c667a0bc49598ea6de09f5127ae18a36f29b559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 18 May 2021 19:44:29 +0200 Subject: [PATCH 2/2] Fix fetching draft releases from GitHub Actions When using GITHUB_TOKEN in Actions, the permissions on a repository are null and therefore we can't check whether the viewer has push access or not. The solution is to unconditionally check for draft releases instead of trying to be smart about it. Draft releases are going to be on top, so we don't have to paginate through all releases in a repository. --- pkg/cmd/release/shared/fetch.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/release/shared/fetch.go b/pkg/cmd/release/shared/fetch.go index 9dad74f0a..a964f9068 100644 --- a/pkg/cmd/release/shared/fetch.go +++ b/pkg/cmd/release/shared/fetch.go @@ -133,11 +133,7 @@ func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName st defer resp.Body.Close() if resp.StatusCode == 404 { - if canPush, err := api.CanPushToRepo(httpClient, baseRepo); err == nil && canPush { - return FindDraftRelease(httpClient, baseRepo, tagName) - } else if err != nil { - return nil, err - } + return FindDraftRelease(httpClient, baseRepo, tagName) } if resp.StatusCode > 299 { @@ -230,11 +226,8 @@ func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagNam return &r, nil } } - - if len(releases) < perPage { - break - } - page++ + //nolint:staticcheck + break } return nil, errors.New("release not found")