feat: add json output for list and view commands
This commit is contained in:
parent
fe23f2fce3
commit
3ccef4e1c6
5 changed files with 192 additions and 52 deletions
152
cmd/actions.go
152
cmd/actions.go
|
|
@ -16,17 +16,17 @@ import (
|
|||
|
||||
// ActionRun represents a workflow run
|
||||
type ActionRun struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
IndexInRepo int64 `json:"index_in_repo"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
CommitSHA string `json:"commit_sha"`
|
||||
PrettyRef string `json:"prettyref"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
Started string `json:"started"`
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
IndexInRepo int64 `json:"index_in_repo"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
CommitSHA string `json:"commit_sha"`
|
||||
PrettyRef string `json:"prettyref"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
Started string `json:"started"`
|
||||
}
|
||||
|
||||
// ActionRunList represents a list of workflow runs
|
||||
|
|
@ -37,19 +37,19 @@ type ActionRunList struct {
|
|||
|
||||
// ActionTask represents a job/task within a workflow run
|
||||
type ActionTask struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HeadBranch string `json:"head_branch"`
|
||||
HeadSHA string `json:"head_sha"`
|
||||
RunNumber int64 `json:"run_number"`
|
||||
Event string `json:"event"`
|
||||
DisplayTitle string `json:"display_title"`
|
||||
Status string `json:"status"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
URL string `json:"url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
RunStartedAt string `json:"run_started_at"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HeadBranch string `json:"head_branch"`
|
||||
HeadSHA string `json:"head_sha"`
|
||||
RunNumber int64 `json:"run_number"`
|
||||
Event string `json:"event"`
|
||||
DisplayTitle string `json:"display_title"`
|
||||
Status string `json:"status"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
URL string `json:"url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
RunStartedAt string `json:"run_started_at"`
|
||||
}
|
||||
|
||||
// ActionTaskList represents a list of tasks/jobs
|
||||
|
|
@ -246,16 +246,20 @@ func init() {
|
|||
// Add flags for run commands
|
||||
addRepoFlags(runListCmd)
|
||||
runListCmd.Flags().IntP("limit", "L", 20, "Maximum number of runs to list")
|
||||
runListCmd.Flags().Bool("json", false, "Output workflow runs as JSON")
|
||||
addRepoFlags(runViewCmd)
|
||||
runViewCmd.Flags().BoolP("verbose", "v", false, "Show job steps")
|
||||
runViewCmd.Flags().BoolP("log", "", false, "View full log for either a run or specific job")
|
||||
runViewCmd.Flags().StringP("job", "j", "", "View a specific job ID from a run")
|
||||
runViewCmd.Flags().BoolP("log-failed", "", false, "View the log for any failed steps in a run or specific job")
|
||||
runViewCmd.Flags().Bool("json", false, "Output workflow run as JSON")
|
||||
|
||||
// Add flags for workflow commands
|
||||
addRepoFlags(workflowListCmd)
|
||||
workflowListCmd.Flags().IntP("limit", "L", 20, "Maximum number of workflows to list")
|
||||
workflowListCmd.Flags().Bool("json", false, "Output workflows as JSON")
|
||||
addRepoFlags(workflowViewCmd)
|
||||
workflowViewCmd.Flags().Bool("json", false, "Output workflow as JSON")
|
||||
addRepoFlags(workflowRunCmd)
|
||||
workflowRunCmd.Flags().StringP("ref", "r", "", "Branch or tag name to run the workflow on (defaults to repository's default branch)")
|
||||
workflowRunCmd.Flags().StringSliceP("field", "f", nil, "Add a string parameter in key=value format (can be used multiple times)")
|
||||
|
|
@ -307,6 +311,10 @@ func runRunList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to list runs: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(runList.WorkflowRuns)
|
||||
}
|
||||
|
||||
if len(runList.WorkflowRuns) == 0 {
|
||||
fmt.Println("No workflow runs found")
|
||||
return nil
|
||||
|
|
@ -364,6 +372,7 @@ func runRunView(cmd *cobra.Command, args []string) error {
|
|||
showLog, _ := cmd.Flags().GetBool("log")
|
||||
jobIDStr, _ := cmd.Flags().GetString("job")
|
||||
showLogFailed, _ := cmd.Flags().GetBool("log-failed")
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
var jobID int64
|
||||
if jobIDStr != "" {
|
||||
|
|
@ -374,6 +383,10 @@ func runRunView(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if jsonOutput && (showLog || showLogFailed) {
|
||||
return fmt.Errorf("--json cannot be used with --log or --log-failed")
|
||||
}
|
||||
|
||||
// Call the API endpoint directly since SDK doesn't have it yet
|
||||
endpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, name, runID)
|
||||
|
||||
|
|
@ -382,6 +395,48 @@ func runRunView(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to get run: %w", err)
|
||||
}
|
||||
|
||||
needsJobs := verbose || showLog || showLogFailed || jobID > 0
|
||||
|
||||
if jsonOutput {
|
||||
var runTasks []ActionTask
|
||||
if needsJobs {
|
||||
tasksEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/tasks", owner, name)
|
||||
var taskList ActionTaskList
|
||||
if err := client.GetJSON(tasksEndpoint, &taskList); err != nil {
|
||||
return fmt.Errorf("failed to get tasks: %w", err)
|
||||
}
|
||||
|
||||
for _, task := range taskList.WorkflowRuns {
|
||||
if task.RunNumber == run.IndexInRepo {
|
||||
runTasks = append(runTasks, task)
|
||||
}
|
||||
}
|
||||
|
||||
if jobID > 0 {
|
||||
var filtered []ActionTask
|
||||
for _, task := range runTasks {
|
||||
if task.ID == jobID {
|
||||
filtered = append(filtered, task)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return fmt.Errorf("job %d not found in this run", jobID)
|
||||
}
|
||||
runTasks = filtered
|
||||
}
|
||||
}
|
||||
|
||||
payload := struct {
|
||||
Run ActionRun `json:"run"`
|
||||
Tasks []ActionTask `json:"tasks,omitempty"`
|
||||
}{
|
||||
Run: run,
|
||||
Tasks: runTasks,
|
||||
}
|
||||
return writeJSON(payload)
|
||||
}
|
||||
|
||||
// Display run information
|
||||
fmt.Printf("Title: %s\n", run.Title)
|
||||
fmt.Printf("Workflow: %s\n", run.WorkflowID)
|
||||
|
|
@ -409,7 +464,6 @@ func runRunView(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
// Fetch jobs if needed for verbose, log, or job-specific views
|
||||
needsJobs := verbose || showLog || showLogFailed || jobID > 0
|
||||
if !needsJobs {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -620,10 +674,17 @@ func runWorkflowList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if len(allWorkflows) == 0 {
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(allWorkflows)
|
||||
}
|
||||
fmt.Println("No workflows found")
|
||||
return nil
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(allWorkflows)
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
if _, err := fmt.Fprintln(w, "NAME\tSTATE\tPATH"); err != nil {
|
||||
return fmt.Errorf("failed to write header: %w", err)
|
||||
|
|
@ -694,26 +755,39 @@ func runWorkflowView(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("workflow '%s' not found", workflowIdentifier)
|
||||
}
|
||||
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
var latestRun *ActionRun
|
||||
|
||||
// Get the latest run for this workflow
|
||||
runsEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?workflow_id=%s&limit=1", owner, name, workflow.Path)
|
||||
var runList ActionRunList
|
||||
if err := client.GetJSON(runsEndpoint, &runList); err == nil && len(runList.WorkflowRuns) > 0 {
|
||||
latestRun = &runList.WorkflowRuns[0]
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
payload := struct {
|
||||
Workflow *Workflow `json:"workflow"`
|
||||
LatestRun *ActionRun `json:"latest_run,omitempty"`
|
||||
}{
|
||||
Workflow: workflow,
|
||||
LatestRun: latestRun,
|
||||
}
|
||||
return writeJSON(payload)
|
||||
}
|
||||
|
||||
// Display workflow information
|
||||
fmt.Printf("Name: %s\n", workflow.Name)
|
||||
fmt.Printf("Path: %s\n", workflow.Path)
|
||||
fmt.Printf("State: %s\n", workflow.State)
|
||||
|
||||
// Get the latest run for this workflow
|
||||
runsEndpoint := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?workflow_id=%s&limit=1", owner, name, workflow.Path)
|
||||
var runList ActionRunList
|
||||
if err := client.GetJSON(runsEndpoint, &runList); err != nil {
|
||||
// If we can't get runs, just display workflow info without latest run
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(runList.WorkflowRuns) > 0 {
|
||||
run := runList.WorkflowRuns[0]
|
||||
if latestRun != nil {
|
||||
fmt.Printf("\nLatest run:\n")
|
||||
fmt.Printf(" Status: %s\n", formatStatus(run.Status))
|
||||
fmt.Printf(" Event: %s\n", run.Event)
|
||||
fmt.Printf(" Ref: %s\n", run.PrettyRef)
|
||||
if createdTime, err := time.Parse(time.RFC3339, run.Created); err == nil {
|
||||
fmt.Printf(" Status: %s\n", formatStatus(latestRun.Status))
|
||||
fmt.Printf(" Event: %s\n", latestRun.Event)
|
||||
fmt.Printf(" Ref: %s\n", latestRun.PrettyRef)
|
||||
if createdTime, err := time.Parse(time.RFC3339, latestRun.Created); err == nil {
|
||||
fmt.Printf(" Created: %s\n", formatTimeSince(createdTime))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
cmd/issue.go
41
cmd/issue.go
|
|
@ -76,8 +76,10 @@ func init() {
|
|||
|
||||
issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
|
||||
issueListCmd.Flags().Bool("json", false, "Output issues as JSON")
|
||||
|
||||
issueViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueViewCmd.Flags().Bool("json", false, "Output issue as JSON")
|
||||
|
||||
issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue")
|
||||
|
|
@ -133,17 +135,26 @@ func runIssueList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to list issues: %w", err)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
nonPRIssues := make([]*gitea.Issue, 0, len(issues))
|
||||
for _, issue := range issues {
|
||||
if issue.PullRequest == nil {
|
||||
nonPRIssues = append(nonPRIssues, issue)
|
||||
}
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(nonPRIssues)
|
||||
}
|
||||
|
||||
if len(nonPRIssues) == 0 {
|
||||
fmt.Printf("No %s issues in %s/%s\n", state, owner, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "NUMBER\tTITLE\tSTATE\n")
|
||||
for _, issue := range issues {
|
||||
if issue.PullRequest == nil {
|
||||
_, _ = fmt.Fprintf(w, "#%d\t%s\t%s\n", issue.Index, issue.Title, issue.State)
|
||||
}
|
||||
for _, issue := range nonPRIssues {
|
||||
_, _ = fmt.Fprintf(w, "#%d\t%s\t%s\n", issue.Index, issue.Title, issue.State)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
|
|
@ -177,6 +188,23 @@ func runIssueView(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to get issue: %w", err)
|
||||
}
|
||||
|
||||
var comments []*gitea.Comment
|
||||
comments, _, err = client.ListIssueComments(owner, name, issueNumber, gitea.ListIssueCommentOptions{})
|
||||
if err != nil {
|
||||
comments = nil
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
payload := struct {
|
||||
Issue *gitea.Issue `json:"issue"`
|
||||
Comments []*gitea.Comment `json:"comments,omitempty"`
|
||||
}{
|
||||
Issue: issue,
|
||||
Comments: comments,
|
||||
}
|
||||
return writeJSON(payload)
|
||||
}
|
||||
|
||||
fmt.Printf("Issue #%d\n", issue.Index)
|
||||
fmt.Printf("Title: %s\n", issue.Title)
|
||||
fmt.Printf("State: %s\n", issue.State)
|
||||
|
|
@ -187,8 +215,7 @@ func runIssueView(cmd *cobra.Command, args []string) error {
|
|||
fmt.Printf("\n%s\n", issue.Body)
|
||||
}
|
||||
|
||||
comments, _, err := client.ListIssueComments(owner, name, issueNumber, gitea.ListIssueCommentOptions{})
|
||||
if err == nil && len(comments) > 0 {
|
||||
if len(comments) > 0 {
|
||||
fmt.Printf("\nComments (%d):\n", len(comments))
|
||||
for _, comment := range comments {
|
||||
fmt.Printf("\n---\n%s (@%s) - %s\n%s\n",
|
||||
|
|
|
|||
12
cmd/json.go
Normal file
12
cmd/json.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
func writeJSON(value any) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(value)
|
||||
}
|
||||
13
cmd/pr.go
13
cmd/pr.go
|
|
@ -8,9 +8,9 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/spf13/cobra"
|
||||
"codeberg.org/romaintb/fgj/internal/api"
|
||||
"codeberg.org/romaintb/fgj/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var prCmd = &cobra.Command{
|
||||
|
|
@ -59,8 +59,10 @@ func init() {
|
|||
|
||||
prListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
prListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
|
||||
prListCmd.Flags().Bool("json", false, "Output pull requests as JSON")
|
||||
|
||||
prViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
prViewCmd.Flags().Bool("json", false, "Output pull request as JSON")
|
||||
|
||||
prCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
prCreateCmd.Flags().StringP("title", "t", "", "Title for the pull request")
|
||||
|
|
@ -111,6 +113,10 @@ func runPRList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to list pull requests: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(prs)
|
||||
}
|
||||
|
||||
if len(prs) == 0 {
|
||||
fmt.Printf("No %s pull requests in %s/%s\n", state, owner, name)
|
||||
return nil
|
||||
|
|
@ -153,6 +159,10 @@ func runPRView(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to get pull request: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(pr)
|
||||
}
|
||||
|
||||
fmt.Printf("Pull Request #%d\n", pr.Index)
|
||||
fmt.Printf("Title: %s\n", pr.Title)
|
||||
fmt.Printf("State: %s\n", pr.State)
|
||||
|
|
@ -279,4 +289,3 @@ func runPRMerge(cmd *cobra.Command, args []string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,8 +72,10 @@ func init() {
|
|||
releaseListCmd.Flags().Bool("draft", false, "Filter by draft status")
|
||||
releaseListCmd.Flags().Bool("prerelease", false, "Filter by prerelease status")
|
||||
releaseListCmd.Flags().Int("limit", 30, "Maximum number of releases to fetch")
|
||||
releaseListCmd.Flags().Bool("json", false, "Output releases as JSON")
|
||||
|
||||
releaseViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
releaseViewCmd.Flags().Bool("json", false, "Output release as JSON")
|
||||
|
||||
releaseCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
releaseCreateCmd.Flags().StringP("title", "t", "", "Release title (defaults to tag)")
|
||||
|
|
@ -146,6 +148,10 @@ func runReleaseList(cmd *cobra.Command, args []string) error {
|
|||
releases = releases[:limit]
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(releases)
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
fmt.Printf("No releases in %s/%s\n", owner, name)
|
||||
return nil
|
||||
|
|
@ -186,6 +192,22 @@ func runReleaseView(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
attachments, err := listReleaseAttachments(client, owner, name, release.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
payload := struct {
|
||||
Release *gitea.Release `json:"release"`
|
||||
Assets []*gitea.Attachment `json:"assets,omitempty"`
|
||||
}{
|
||||
Release: release,
|
||||
Assets: attachments,
|
||||
}
|
||||
return writeJSON(payload)
|
||||
}
|
||||
|
||||
fmt.Printf("Release %s\n", release.TagName)
|
||||
fmt.Printf("Title: %s\n", release.Title)
|
||||
fmt.Printf("Type: %s\n", releaseType(release))
|
||||
|
|
@ -206,10 +228,6 @@ func runReleaseView(cmd *cobra.Command, args []string) error {
|
|||
fmt.Printf("\n%s\n", release.Note)
|
||||
}
|
||||
|
||||
attachments, err := listReleaseAttachments(client, owner, name, release.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(attachments) > 0 {
|
||||
fmt.Printf("\nAssets (%d):\n", len(attachments))
|
||||
for _, asset := range attachments {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue