Preliminary gh release commands

This commit is contained in:
Mislav Marohnić 2020-08-19 18:25:02 +02:00
parent 0cc59488a8
commit c4f5d6db58
9 changed files with 477 additions and 7 deletions

View file

@ -0,0 +1,114 @@
package list
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/spf13/cobra"
)
type CreateOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
TagName string
}
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
opts := &CreateOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}
cmd := &cobra.Command{
Use: "create <tag>",
Short: "Create a new release",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
opts.TagName = args[0]
if runF != nil {
return runF(opts)
}
return createRun(opts)
},
}
return cmd
}
func createRun(opts *CreateOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
baseRepo, err := opts.BaseRepo()
if err != nil {
return err
}
params := map[string]interface{}{
"tag_name": opts.TagName,
}
bodyBytes, err := json.Marshal(params)
if err != nil {
return err
}
path := fmt.Sprintf("repos/%s/%s/releases", baseRepo.RepoOwner(), baseRepo.RepoName())
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
success := resp.StatusCode >= 200 && resp.StatusCode < 300
if !success {
return api.HandleHTTPError(resp)
}
if resp.StatusCode == http.StatusNoContent {
return nil
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
var newRelease struct {
HTMLURL string `json:"html_url"`
AssetsURL string `json:"assets_url"`
}
err = json.Unmarshal(b, &newRelease)
if err != nil {
return err
}
fmt.Fprintf(opts.IO.Out, "%s\n", newRelease.HTMLURL)
return nil
}

View file

@ -0,0 +1,71 @@
package list
import (
"context"
"net/http"
"time"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
"github.com/shurcooL/githubv4"
"github.com/shurcooL/graphql"
)
type Release struct {
Name string
TagName string
IsDraft bool
IsPrerelease bool
PublishedAt time.Time
}
func fetchReleases(httpClient *http.Client, repo ghrepo.Interface, limit int) ([]Release, error) {
var query struct {
Repository struct {
Releases struct {
Nodes []Release
PageInfo struct {
HasNextPage bool
EndCursor string
}
} `graphql:"releases(first: $perPage, orderBy: {field: CREATED_AT, direction: DESC}, after: $endCursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
perPage := limit
if limit > 100 {
perPage = 100
}
variables := map[string]interface{}{
"owner": githubv4.String(repo.RepoOwner()),
"name": githubv4.String(repo.RepoName()),
"perPage": githubv4.Int(perPage),
"endCursor": (*githubv4.String)(nil),
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(repo.RepoHost()), httpClient)
var releases []Release
loop:
for {
err := gql.QueryNamed(context.Background(), "RepositoryReleaseList", &query, variables)
if err != nil {
return nil, err
}
for _, r := range query.Repository.Releases.Nodes {
releases = append(releases, r)
if len(releases) == limit {
break loop
}
}
if !query.Repository.Releases.PageInfo.HasNextPage {
break
}
variables["endCursor"] = githubv4.String(query.Repository.Releases.PageInfo.EndCursor)
}
return releases, nil
}

View file

@ -0,0 +1,79 @@
package list
import (
"net/http"
"time"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/pkg/text"
"github.com/cli/cli/utils"
"github.com/spf13/cobra"
)
type ListOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
LimitResults int
}
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
opts := &ListOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}
cmd := &cobra.Command{
Use: "list",
Short: "List releases in a repository",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
if runF != nil {
return runF(opts)
}
return listRun(opts)
},
}
cmd.Flags().IntVarP(&opts.LimitResults, "limit", "L", 30, "Maximum number of items to fetch")
return cmd
}
func listRun(opts *ListOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
baseRepo, err := opts.BaseRepo()
if err != nil {
return err
}
releases, err := fetchReleases(httpClient, baseRepo, opts.LimitResults)
if err != nil {
return err
}
now := time.Now()
table := utils.NewTablePrinter(opts.IO)
for _, rel := range releases {
table.AddField(rel.TagName, nil, nil)
table.AddField(text.ReplaceExcessiveWhitespace(rel.Name), nil, nil)
table.AddField(utils.FuzzyAgo(now.Sub(rel.PublishedAt)), nil, nil)
table.EndRow()
}
err = table.Render()
if err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,27 @@
package release
import (
cmdCreate "github.com/cli/cli/pkg/cmd/release/create"
cmdList "github.com/cli/cli/pkg/cmd/release/list"
cmdView "github.com/cli/cli/pkg/cmd/release/view"
"github.com/cli/cli/pkg/cmdutil"
"github.com/spf13/cobra"
)
func NewCmdRelease(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "release <command>",
Short: "Manage GitHub releases",
Annotations: map[string]string{
"IsCore": "true",
},
}
cmdutil.EnableRepoOverride(cmd, f)
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
cmd.AddCommand(cmdList.NewCmdList(f, nil))
cmd.AddCommand(cmdView.NewCmdView(f, nil))
return cmd
}

View file

@ -0,0 +1,51 @@
package view
import (
"context"
"net/http"
"time"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
"github.com/shurcooL/githubv4"
"github.com/shurcooL/graphql"
)
type Release struct {
TagName string
Name string
Description string
URL string
IsDraft bool
IsPrerelease bool
PublishedAt time.Time
Author struct {
Login string
}
ReleaseAssets struct {
Nodes []struct {
Name string
Size int
}
} `graphql:"releaseAssets(first: 100)"`
}
func fetchRelease(httpClient *http.Client, repo ghrepo.Interface, tagName string) (*Release, error) {
var query struct {
Repository struct {
Release *Release `graphql:"release(tagName: $tagName)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
variables := map[string]interface{}{
"owner": githubv4.String(repo.RepoOwner()),
"name": githubv4.String(repo.RepoName()),
"tagName": githubv4.String(tagName),
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(repo.RepoHost()), httpClient)
err := gql.QueryNamed(context.Background(), "RepositoryReleaseByTag", &query, variables)
return query.Repository.Release, err
}

View file

@ -0,0 +1,75 @@
package view
import (
"fmt"
"net/http"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/utils"
"github.com/spf13/cobra"
)
type ViewOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
TagName string
}
func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command {
opts := &ViewOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}
cmd := &cobra.Command{
Use: "view <tag>",
Short: "View information about a release",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
opts.TagName = args[0]
if runF != nil {
return runF(opts)
}
return viewRun(opts)
},
}
return cmd
}
func viewRun(opts *ViewOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
baseRepo, err := opts.BaseRepo()
if err != nil {
return err
}
release, err := fetchRelease(httpClient, baseRepo, opts.TagName)
if err != nil {
return err
}
fmt.Fprintf(opts.IO.Out, "%s\n", release.TagName)
renderedDescription, err := utils.RenderMarkdown(release.Description)
if err != nil {
return err
}
fmt.Fprintln(opts.IO.Out, renderedDescription)
fmt.Fprintf(opts.IO.Out, "%s\n", release.URL)
return nil
}

View file

@ -16,6 +16,7 @@ import (
gistCmd "github.com/cli/cli/pkg/cmd/gist"
issueCmd "github.com/cli/cli/pkg/cmd/issue"
prCmd "github.com/cli/cli/pkg/cmd/pr"
releaseCmd "github.com/cli/cli/pkg/cmd/release"
repoCmd "github.com/cli/cli/pkg/cmd/repo"
creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits"
"github.com/cli/cli/pkg/cmdutil"
@ -114,6 +115,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
cmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
cmd.AddCommand(releaseCmd.NewCmdRelease(&repoResolvingCmdFactory))
cmd.AddCommand(repoCmd.NewCmdRepo(&repoResolvingCmdFactory))
return cmd