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:
sid 2026-03-23 11:42:44 -06:00
parent 7c0dcc8696
commit 113505de95
29 changed files with 3131 additions and 542 deletions

View file

@ -2,9 +2,7 @@ package cmd
import (
"fmt"
"os"
"strings"
"text/tabwriter"
"code.gitea.io/sdk/gitea"
"forgejo.zerova.net/sid/fgj-sid/internal/api"
@ -72,6 +70,9 @@ var labelDeleteCmd = &cobra.Command{
Example: ` # Delete a label
fgj label delete bug
# Delete without confirmation
fgj label delete bug -y
# Delete a label from a specific repository
fgj label delete bug -R owner/repo`,
Args: cobra.ExactArgs(1),
@ -86,20 +87,21 @@ func init() {
labelCmd.AddCommand(labelDeleteCmd)
labelListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
labelListCmd.Flags().Bool("json", false, "Output as JSON")
addJSONFlags(labelListCmd, "Output as JSON")
labelCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
labelCreateCmd.Flags().StringP("color", "c", "", "Label color (hex, e.g. 00ff00)")
labelCreateCmd.Flags().StringP("description", "d", "", "Label description")
labelCreateCmd.Flags().Bool("json", false, "Output as JSON")
addJSONFlags(labelCreateCmd, "Output as JSON")
labelEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
labelEditCmd.Flags().String("name", "", "New name for the label")
labelEditCmd.Flags().StringP("color", "c", "", "New color (hex, e.g. 00ff00)")
labelEditCmd.Flags().StringP("description", "d", "", "New description")
labelEditCmd.Flags().Bool("json", false, "Output as JSON")
addJSONFlags(labelEditCmd, "Output as JSON")
labelDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
labelDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
}
func newLabelClient(cmd *cobra.Command) (*api.Client, string, string, error) {
@ -144,29 +146,28 @@ func runLabelList(cmd *cobra.Command, args []string) error {
return err
}
ios.StartSpinner("Fetching labels...")
labels, _, err := client.ListRepoLabels(owner, name, gitea.ListLabelsOptions{})
ios.StopSpinner()
if err != nil {
return fmt.Errorf("failed to list labels: %w", err)
}
jsonFlag, _ := cmd.Flags().GetBool("json")
if jsonFlag {
return writeJSON(labels)
if wantJSON(cmd) {
return outputJSON(cmd, labels)
}
if len(labels) == 0 {
fmt.Println("No labels found")
fmt.Fprintln(ios.Out, "No labels found")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "NAME\tCOLOR\tDESCRIPTION\n")
tp := ios.NewTablePrinter()
tp.AddHeader("NAME", "COLOR", "DESCRIPTION")
for _, l := range labels {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", l.Name, l.Color, l.Description)
tp.AddRow(l.Name, l.Color, l.Description)
}
_ = w.Flush()
return nil
return tp.Render()
}
func runLabelCreate(cmd *cobra.Command, args []string) error {
@ -179,21 +180,23 @@ func runLabelCreate(cmd *cobra.Command, args []string) error {
return err
}
ios.StartSpinner("Creating label...")
label, _, err := client.CreateLabel(owner, name, gitea.CreateLabelOption{
Name: labelName,
Color: color,
Description: description,
})
ios.StopSpinner()
if err != nil {
return fmt.Errorf("failed to create label: %w", err)
}
jsonFlag, _ := cmd.Flags().GetBool("json")
if jsonFlag {
return writeJSON(label)
if wantJSON(cmd) {
return outputJSON(cmd, label)
}
fmt.Printf("Label created: %s\n", label.Name)
cs := ios.ColorScheme()
fmt.Fprintf(ios.Out, "%s Label created: %s\n", cs.SuccessIcon(), label.Name)
return nil
}
@ -205,7 +208,9 @@ func runLabelEdit(cmd *cobra.Command, args []string) error {
return err
}
ios.StartSpinner("Fetching label...")
existing, err := findLabelByName(client, owner, name, labelName)
ios.StopSpinner()
if err != nil {
return err
}
@ -233,46 +238,57 @@ func runLabelEdit(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no changes specified; use flags like --name, --color, or --description")
}
ios.StartSpinner("Updating label...")
label, _, err := client.EditLabel(owner, name, existing.ID, opt)
ios.StopSpinner()
if err != nil {
return fmt.Errorf("failed to edit label: %w", err)
}
jsonFlag, _ := cmd.Flags().GetBool("json")
if jsonFlag {
return writeJSON(label)
if wantJSON(cmd) {
return outputJSON(cmd, label)
}
fmt.Printf("Label updated: %s\n", label.Name)
cs := ios.ColorScheme()
fmt.Fprintf(ios.Out, "%s Label updated: %s\n", cs.SuccessIcon(), label.Name)
return nil
}
func runLabelDelete(cmd *cobra.Command, args []string) error {
labelName := args[0]
yes, _ := cmd.Flags().GetBool("yes")
client, owner, name, err := newLabelClient(cmd)
if err != nil {
return err
}
ios.StartSpinner("Fetching label...")
existing, err := findLabelByName(client, owner, name, labelName)
ios.StopSpinner()
if err != nil {
return err
}
fmt.Printf("Are you sure you want to delete label %q? (y/N): ", labelName)
var confirm string
_, _ = fmt.Scanln(&confirm)
if strings.ToLower(confirm) != "y" {
fmt.Println("Aborted")
return nil
if !yes {
confirmed, err := ios.ConfirmAction(fmt.Sprintf("Delete label %q?", labelName))
if err != nil {
return err
}
if !confirmed {
fmt.Fprintln(ios.ErrOut, "Aborted")
return nil
}
}
ios.StartSpinner("Deleting label...")
_, err = client.DeleteLabel(owner, name, existing.ID)
ios.StopSpinner()
if err != nil {
return fmt.Errorf("failed to delete label: %w", err)
}
fmt.Printf("Label deleted: %s\n", labelName)
cs := ios.ColorScheme()
fmt.Fprintf(ios.Out, "%s Label deleted: %s\n", cs.SuccessIcon(), labelName)
return nil
}