feat: v0.3.0d — add PR checks, iostreams, aliases, and broad enhancements
Add PR checks command, iostreams/text packages for colored table output, top-level run/workflow aliases matching gh CLI structure. Enhance actions, issues, PRs, releases, repos, labels, milestones, and wiki commands with improved flags, JSON output, and error handling.
This commit is contained in:
parent
7c0dcc8696
commit
113505de95
29 changed files with 3131 additions and 542 deletions
122
cmd/milestone.go
122
cmd/milestone.go
|
|
@ -2,15 +2,14 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/api"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/config"
|
||||
"forgejo.zerova.net/sid/fgj-sid/internal/text"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -45,6 +44,9 @@ var milestoneViewCmd = &cobra.Command{
|
|||
# View by title
|
||||
fgj milestone view "v1.0"
|
||||
|
||||
# Open in browser
|
||||
fgj milestone view "v1.0" --web
|
||||
|
||||
# Output as JSON
|
||||
fgj milestone view "v1.0" --json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
|
@ -91,7 +93,10 @@ var milestoneDeleteCmd = &cobra.Command{
|
|||
fgj milestone delete "v1.0"
|
||||
|
||||
# Delete by ID
|
||||
fgj milestone delete 1`,
|
||||
fgj milestone delete 1
|
||||
|
||||
# Delete without confirmation
|
||||
fgj milestone delete "v1.0" -y`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runMilestoneDelete,
|
||||
}
|
||||
|
|
@ -106,24 +111,26 @@ func init() {
|
|||
|
||||
milestoneListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
milestoneListCmd.Flags().String("state", "open", "Filter by state: open, closed, all")
|
||||
milestoneListCmd.Flags().Bool("json", false, "Output milestones as JSON")
|
||||
addJSONFlags(milestoneListCmd, "Output milestones as JSON")
|
||||
|
||||
milestoneViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
milestoneViewCmd.Flags().Bool("json", false, "Output milestone as JSON")
|
||||
addJSONFlags(milestoneViewCmd, "Output milestone as JSON")
|
||||
milestoneViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")
|
||||
|
||||
milestoneCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
milestoneCreateCmd.Flags().StringP("description", "d", "", "Description of the milestone")
|
||||
milestoneCreateCmd.Flags().String("due", "", "Due date in YYYY-MM-DD format")
|
||||
milestoneCreateCmd.Flags().Bool("json", false, "Output created milestone as JSON")
|
||||
addJSONFlags(milestoneCreateCmd, "Output created milestone as JSON")
|
||||
|
||||
milestoneEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
milestoneEditCmd.Flags().String("title", "", "New title for the milestone")
|
||||
milestoneEditCmd.Flags().StringP("description", "d", "", "New description for the milestone")
|
||||
milestoneEditCmd.Flags().String("due", "", "Due date in YYYY-MM-DD format")
|
||||
milestoneEditCmd.Flags().String("state", "", "New state: open or closed")
|
||||
milestoneEditCmd.Flags().Bool("json", false, "Output updated milestone as JSON")
|
||||
addJSONFlags(milestoneEditCmd, "Output updated milestone as JSON")
|
||||
|
||||
milestoneDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
milestoneDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
|
||||
}
|
||||
|
||||
// resolveMilestone resolves a title-or-id argument to a milestone.
|
||||
|
|
@ -193,35 +200,40 @@ func runMilestoneList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("invalid state: %s", state)
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching milestones...")
|
||||
milestones, _, err := client.ListRepoMilestones(owner, name, gitea.ListMilestoneOption{
|
||||
State: stateType,
|
||||
})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list milestones: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(milestones)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, milestones)
|
||||
}
|
||||
|
||||
if len(milestones) == 0 {
|
||||
fmt.Printf("No %s milestones in %s/%s\n", state, owner, name)
|
||||
fmt.Fprintf(ios.Out, "No %s milestones in %s/%s\n", state, owner, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "ID\tTITLE\tSTATE\tDUE DATE\tOPEN/CLOSED ISSUES\n")
|
||||
tp := ios.NewTablePrinter()
|
||||
tp.AddHeader("ID", "TITLE", "STATE", "DUE DATE", "OPEN/CLOSED ISSUES")
|
||||
for _, ms := range milestones {
|
||||
due := ""
|
||||
if ms.Deadline != nil {
|
||||
due = ms.Deadline.Format("2006-01-02")
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d/%d\n",
|
||||
ms.ID, ms.Title, ms.State, due, ms.OpenIssues, ms.ClosedIssues)
|
||||
tp.AddRow(
|
||||
fmt.Sprintf("%d", ms.ID),
|
||||
ms.Title,
|
||||
string(ms.State),
|
||||
due,
|
||||
fmt.Sprintf("%d/%d", ms.OpenIssues, ms.ClosedIssues),
|
||||
)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
return nil
|
||||
return tp.Render()
|
||||
}
|
||||
|
||||
func runMilestoneView(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -242,32 +254,45 @@ func runMilestoneView(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching milestone...")
|
||||
ms, err := resolveMilestone(client, owner, name, args[0])
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(ms)
|
||||
if web, _ := cmd.Flags().GetBool("web"); web {
|
||||
// Milestones don't have HTMLURL in the API, construct it
|
||||
cfg2, _ := config.Load()
|
||||
host, _ := cfg2.GetHost("", getDetectedHost())
|
||||
url := fmt.Sprintf("https://%s/%s/%s/milestone/%d", host.Hostname, owner, name, ms.ID)
|
||||
return ios.OpenInBrowser(url)
|
||||
}
|
||||
|
||||
fmt.Printf("ID: %d\n", ms.ID)
|
||||
fmt.Printf("Title: %s\n", ms.Title)
|
||||
fmt.Printf("State: %s\n", ms.State)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, ms)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
isTTY := ios.IsStdoutTTY()
|
||||
|
||||
fmt.Fprintf(ios.Out, "ID: %d\n", ms.ID)
|
||||
fmt.Fprintf(ios.Out, "Title: %s\n", cs.Bold(ms.Title))
|
||||
fmt.Fprintf(ios.Out, "State: %s\n", ms.State)
|
||||
if ms.Description != "" {
|
||||
fmt.Printf("Description: %s\n", ms.Description)
|
||||
fmt.Fprintf(ios.Out, "Description: %s\n", ms.Description)
|
||||
}
|
||||
if ms.Deadline != nil {
|
||||
fmt.Printf("Due Date: %s\n", ms.Deadline.Format("2006-01-02"))
|
||||
fmt.Fprintf(ios.Out, "Due Date: %s\n", ms.Deadline.Format("2006-01-02"))
|
||||
}
|
||||
fmt.Printf("Open Issues: %d\n", ms.OpenIssues)
|
||||
fmt.Printf("Closed Issues: %d\n", ms.ClosedIssues)
|
||||
fmt.Printf("Created: %s\n", ms.Created.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(ios.Out, "Open Issues: %d\n", ms.OpenIssues)
|
||||
fmt.Fprintf(ios.Out, "Closed Issues: %d\n", ms.ClosedIssues)
|
||||
fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(ms.Created, isTTY))
|
||||
if ms.Updated != nil {
|
||||
fmt.Printf("Updated: %s\n", ms.Updated.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(*ms.Updated, isTTY))
|
||||
}
|
||||
if ms.Closed != nil {
|
||||
fmt.Printf("Closed: %s\n", ms.Closed.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(ios.Out, "Closed: %s\n", text.FormatDate(*ms.Closed, isTTY))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -308,16 +333,19 @@ func runMilestoneCreate(cmd *cobra.Command, args []string) error {
|
|||
opt.Deadline = deadline
|
||||
}
|
||||
|
||||
ios.StartSpinner("Creating milestone...")
|
||||
ms, _, err := client.CreateMilestone(owner, name, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create milestone: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(ms)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, ms)
|
||||
}
|
||||
|
||||
fmt.Printf("Milestone created: %s\n", ms.Title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Milestone created: %s\n", cs.SuccessIcon(), ms.Title)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -340,7 +368,9 @@ func runMilestoneEdit(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching milestone...")
|
||||
ms, err := resolveMilestone(client, owner, name, args[0])
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -389,22 +419,26 @@ func runMilestoneEdit(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("no changes specified; use flags like --title, --description, --due, or --state")
|
||||
}
|
||||
|
||||
ios.StartSpinner("Updating milestone...")
|
||||
updated, _, err := client.EditMilestone(owner, name, ms.ID, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to edit milestone: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput, _ := cmd.Flags().GetBool("json"); jsonOutput {
|
||||
return writeJSON(updated)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, updated)
|
||||
}
|
||||
|
||||
fmt.Printf("Milestone updated: %s\n", updated.Title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Milestone updated: %s\n", cs.SuccessIcon(), updated.Title)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runMilestoneDelete(cmd *cobra.Command, args []string) error {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
yes, _ := cmd.Flags().GetBool("yes")
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
|
|
@ -421,17 +455,33 @@ func runMilestoneDelete(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching milestone...")
|
||||
ms, err := resolveMilestone(client, owner, name, args[0])
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !yes {
|
||||
confirmed, err := ios.ConfirmAction(fmt.Sprintf("Delete milestone %q?", ms.Title))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Fprintln(ios.ErrOut, "Aborted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Deleting milestone...")
|
||||
_, err = client.DeleteMilestone(owner, name, ms.ID)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete milestone: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Milestone deleted: %s\n", ms.Title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Milestone deleted: %s\n", cs.SuccessIcon(), ms.Title)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue