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 }