From 83fb7c0d267cdd409281bd916acbd0a7e48875e8 Mon Sep 17 00:00:00 2001 From: Danilo Sousa Date: Wed, 9 Sep 2026 20:57:31 -0300 Subject: [PATCH] Make issues create honor --output json Same hole and same shape as the merged pulls fix (#1111): --output parses on issues create through the urfave/cli v3 ancestor-flag cascade but was never read, so the action always printed glamour markdown plus a second bare URL line. task.CreateIssue now returns the created issue and the cmd layer switches on --output, emitting compact lean JSON (index, title, url, state), matching the post-review shape of writeCreatedPullAsJSON. Without the flag the output stays byte-identical: the same print.IssueDetails and HTMLURL println calls just moved to the callers. Signed-off-by: Danilo Sousa --- cmd/issues/create.go | 46 +++++++++++++++++++++++++++++--- cmd/issues/create_test.go | 41 ++++++++++++++++++++++++++++ modules/interact/issue_create.go | 13 ++++++++- modules/task/issue_create.go | 15 ++++------- 4 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 cmd/issues/create_test.go diff --git a/cmd/issues/create.go b/cmd/issues/create.go index 8b0d7d53..9cfd72bd 100644 --- a/cmd/issues/create.go +++ b/cmd/issues/create.go @@ -5,13 +5,18 @@ package issues import ( stdctx "context" + "encoding/json" + "fmt" + "io" + + gitea "gitea.dev/sdk" + "github.com/urfave/cli/v3" "gitea.dev/tea/cmd/flags" "gitea.dev/tea/modules/context" "gitea.dev/tea/modules/interact" + "gitea.dev/tea/modules/print" "gitea.dev/tea/modules/task" - - "github.com/urfave/cli/v3" ) // CmdIssuesCreate represents a sub command of issues to create issue @@ -47,9 +52,44 @@ func runIssuesCreate(requestCtx stdctx.Context, cmd *cli.Command) error { return err } - return task.CreateIssue(requestCtx, ctx.Login, + issue, err := task.CreateIssue(requestCtx, ctx.Login, ctx.Owner, ctx.Repo, *opts, ) + if err != nil { + return err + } + + if ctx.IsSet("output") { + switch ctx.String("output") { + case "json": + return writeCreatedIssueAsJSON(ctx.Writer, issue) + } + } + + print.IssueDetails(issue, nil) + + fmt.Println(issue.HTMLURL) + + return nil +} + +// createdIssueJSON is the machine-readable representation of a freshly +// created issue, mirroring the create-PR equivalent in cmd/pulls/create.go +// (createdPullJSON). +type createdIssueJSON struct { + Index int64 `json:"index"` + Title string `json:"title"` + URL string `json:"url"` + State gitea.StateType `json:"state"` +} + +func writeCreatedIssueAsJSON(w io.Writer, issue *gitea.Issue) error { + return json.NewEncoder(w).Encode(createdIssueJSON{ + Index: issue.Index, + Title: issue.Title, + URL: issue.HTMLURL, + State: issue.State, + }) } diff --git a/cmd/issues/create_test.go b/cmd/issues/create_test.go new file mode 100644 index 00000000..274b312a --- /dev/null +++ b/cmd/issues/create_test.go @@ -0,0 +1,41 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues + +import ( + "bytes" + "encoding/json" + "testing" + + gitea "gitea.dev/sdk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteCreatedIssueAsJSON(t *testing.T) { + issue := &gitea.Issue{ + Index: 42, + Title: "test title", + HTMLURL: "https://gitea.example.com/owner/repo/issues/42", + State: gitea.StateOpen, + } + + var buf bytes.Buffer + require.NoError(t, writeCreatedIssueAsJSON(&buf, issue)) + + var got map[string]any + require.NoError(t, json.Unmarshal(buf.Bytes(), &got)) + + assert.Equal(t, float64(42), got["index"]) + assert.Equal(t, "test title", got["title"]) + assert.Equal(t, "https://gitea.example.com/owner/repo/issues/42", got["url"]) + assert.Equal(t, "open", got["state"]) + + // exactly the lean field set, nothing extra + assert.Len(t, got, 4) + + // machine-readable output must not contain terminal escape sequences + assert.NotContains(t, buf.String(), "\x1b") +} diff --git a/modules/interact/issue_create.go b/modules/interact/issue_create.go index 3dee358e..c1202499 100644 --- a/modules/interact/issue_create.go +++ b/modules/interact/issue_create.go @@ -5,11 +5,13 @@ package interact import ( "context" + "fmt" "strings" gitea "gitea.dev/sdk" "gitea.dev/tea/modules/config" + "gitea.dev/tea/modules/print" "gitea.dev/tea/modules/task" "gitea.dev/tea/modules/theme" @@ -34,7 +36,16 @@ func CreateIssue(ctx context.Context, login *config.Login, owner, repo string) e return err } - return task.CreateIssue(ctx, login, owner, repo, opts) + issue, err := task.CreateIssue(ctx, login, owner, repo, opts) + if err != nil { + return err + } + + print.IssueDetails(issue, nil) + + fmt.Println(issue.HTMLURL) + + return nil } func promptIssueProperties(ctx context.Context, login *config.Login, owner, repo string, o *gitea.CreateIssueOption) error { diff --git a/modules/task/issue_create.go b/modules/task/issue_create.go index 35b9c245..9d326c37 100644 --- a/modules/task/issue_create.go +++ b/modules/task/issue_create.go @@ -10,24 +10,19 @@ import ( gitea "gitea.dev/sdk" "gitea.dev/tea/modules/config" - "gitea.dev/tea/modules/print" ) -// CreateIssue creates an issue in the given repo and prints the result -func CreateIssue(requestCtx stdctx.Context, rlogin *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) error { +// CreateIssue creates an issue in the given repo and returns the created issue +func CreateIssue(requestCtx stdctx.Context, rlogin *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) (*gitea.Issue, error) { // title is required if len(opts.Title) == 0 { - return fmt.Errorf("title is required") + return nil, fmt.Errorf("title is required") } issue, _, err := rlogin.Client().Issues.CreateIssue(requestCtx, repoOwner, repoName, opts) if err != nil { - return fmt.Errorf("could not create issue: %s", err) + return nil, fmt.Errorf("could not create issue: %s", err) } - print.IssueDetails(issue, nil) - - fmt.Println(issue.HTMLURL) - - return nil + return issue, nil }