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
210
cmd/repo.go
210
cmd/repo.go
|
|
@ -6,11 +6,11 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
|
|
@ -78,17 +78,34 @@ var repoEditCmd = &cobra.Command{
|
|||
# Change default branch
|
||||
fgj repo edit --default-branch develop
|
||||
|
||||
# Rename a repository
|
||||
fgj repo edit owner/repo --name new-name
|
||||
|
||||
# Edit current repo (auto-detected from git context)
|
||||
fgj repo edit --public`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runRepoEdit,
|
||||
}
|
||||
|
||||
var repoRenameCmd = &cobra.Command{
|
||||
Use: "rename <new-name>",
|
||||
Short: "Rename a repository",
|
||||
Long: "Rename an existing repository. This is a shorthand for `fgj repo edit --name <new-name>`.",
|
||||
Example: ` # Rename current repo
|
||||
fgj repo rename new-name
|
||||
|
||||
# Rename a specific repo
|
||||
fgj repo rename new-name -R owner/old-name`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRepoRename,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(repoCmd)
|
||||
repoCmd.AddCommand(repoCloneCmd)
|
||||
repoCmd.AddCommand(repoCreateCmd)
|
||||
repoCmd.AddCommand(repoEditCmd)
|
||||
repoCmd.AddCommand(repoRenameCmd)
|
||||
repoCmd.AddCommand(repoForkCmd)
|
||||
repoCmd.AddCommand(repoListCmd)
|
||||
repoCmd.AddCommand(repoViewCmd)
|
||||
|
|
@ -104,16 +121,25 @@ func init() {
|
|||
repoCreateCmd.Flags().StringP("team", "t", "", "Team name to be granted access (org repos only)")
|
||||
repoCreateCmd.MarkFlagsMutuallyExclusive("public", "private")
|
||||
|
||||
addJSONFlags(repoViewCmd, "Output repository as JSON")
|
||||
repoViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")
|
||||
|
||||
addJSONFlags(repoListCmd, "Output repositories as JSON")
|
||||
|
||||
repoCloneCmd.Flags().StringP("protocol", "p", "https", "Clone protocol: https or ssh")
|
||||
|
||||
repoEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
repoEditCmd.Flags().String("name", "", "Rename the repository")
|
||||
repoEditCmd.Flags().StringP("description", "d", "", "Repository description")
|
||||
repoEditCmd.Flags().String("homepage", "", "Repository home page URL")
|
||||
repoEditCmd.Flags().String("default-branch", "", "Default branch name")
|
||||
repoEditCmd.Flags().Bool("private", false, "Make the repository private")
|
||||
repoEditCmd.Flags().Bool("public", false, "Make the repository public")
|
||||
repoEditCmd.Flags().Bool("json", false, "Output updated repository as JSON")
|
||||
addJSONFlags(repoEditCmd, "Output updated repository as JSON")
|
||||
repoEditCmd.MarkFlagsMutuallyExclusive("public", "private")
|
||||
|
||||
repoRenameCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
addJSONFlags(repoRenameCmd, "Output updated repository as JSON")
|
||||
}
|
||||
|
||||
func runRepoView(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -137,23 +163,36 @@ func runRepoView(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching repository...")
|
||||
repository, _, err := client.GetRepo(owner, name)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get repository: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Repository: %s/%s\n", repository.Owner.UserName, repository.Name)
|
||||
fmt.Printf("Description: %s\n", repository.Description)
|
||||
fmt.Printf("URL: %s\n", repository.HTMLURL)
|
||||
fmt.Printf("Clone URL (HTTPS): %s\n", repository.CloneURL)
|
||||
fmt.Printf("Clone URL (SSH): %s\n", repository.SSHURL)
|
||||
fmt.Printf("Default Branch: %s\n", repository.DefaultBranch)
|
||||
fmt.Printf("Stars: %d\n", repository.Stars)
|
||||
fmt.Printf("Forks: %d\n", repository.Forks)
|
||||
fmt.Printf("Open Issues: %d\n", repository.OpenIssues)
|
||||
fmt.Printf("Private: %v\n", repository.Private)
|
||||
fmt.Printf("Created: %s\n", repository.Created.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("Updated: %s\n", repository.Updated.Format("2006-01-02 15:04:05"))
|
||||
if web, _ := cmd.Flags().GetBool("web"); web {
|
||||
return ios.OpenInBrowser(repository.HTMLURL)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repository)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
isTTY := ios.IsStdoutTTY()
|
||||
|
||||
fmt.Fprintf(ios.Out, "Repository: %s\n", cs.Bold(fmt.Sprintf("%s/%s", repository.Owner.UserName, repository.Name)))
|
||||
fmt.Fprintf(ios.Out, "Description: %s\n", repository.Description)
|
||||
fmt.Fprintf(ios.Out, "URL: %s\n", repository.HTMLURL)
|
||||
fmt.Fprintf(ios.Out, "Clone URL (HTTPS): %s\n", repository.CloneURL)
|
||||
fmt.Fprintf(ios.Out, "Clone URL (SSH): %s\n", repository.SSHURL)
|
||||
fmt.Fprintf(ios.Out, "Default Branch: %s\n", repository.DefaultBranch)
|
||||
fmt.Fprintf(ios.Out, "Stars: %d\n", repository.Stars)
|
||||
fmt.Fprintf(ios.Out, "Forks: %d\n", repository.Forks)
|
||||
fmt.Fprintf(ios.Out, "Open Issues: %d\n", repository.OpenIssues)
|
||||
fmt.Fprintf(ios.Out, "Private: %v\n", repository.Private)
|
||||
fmt.Fprintf(ios.Out, "Created: %s\n", text.FormatDate(repository.Created, isTTY))
|
||||
fmt.Fprintf(ios.Out, "Updated: %s\n", text.FormatDate(repository.Updated, isTTY))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -169,37 +208,39 @@ func runRepoList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching repositories...")
|
||||
user, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to get user info: %w", err)
|
||||
}
|
||||
|
||||
repos, _, err := client.ListUserRepos(user.UserName, gitea.ListReposOptions{})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list repositories: %w", err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repos)
|
||||
}
|
||||
|
||||
if len(repos) == 0 {
|
||||
fmt.Println("No repositories found")
|
||||
fmt.Fprintln(ios.Out, "No repositories found")
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "NAME\tVISIBILITY\tDESCRIPTION\n")
|
||||
tp := ios.NewTablePrinter()
|
||||
tp.AddHeader("NAME", "VISIBILITY", "DESCRIPTION")
|
||||
for _, repo := range repos {
|
||||
visibility := "public"
|
||||
if repo.Private {
|
||||
visibility = "private"
|
||||
}
|
||||
desc := repo.Description
|
||||
if len(desc) > 50 {
|
||||
desc = desc[:47] + "..."
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s/%s\t%s\t%s\n", repo.Owner.UserName, repo.Name, visibility, desc)
|
||||
desc := text.Truncate(repo.Description, 50)
|
||||
tp.AddRow(fmt.Sprintf("%s/%s", repo.Owner.UserName, repo.Name), visibility, desc)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
return nil
|
||||
return tp.Render()
|
||||
}
|
||||
|
||||
func runRepoClone(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -221,7 +262,9 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Fetching repository info...")
|
||||
repository, _, err := client.GetRepo(owner, name)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get repository: %w", err)
|
||||
}
|
||||
|
|
@ -241,7 +284,7 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
|
|||
destination = name
|
||||
}
|
||||
|
||||
fmt.Printf("Cloning %s/%s to %s...\n", owner, name, destination)
|
||||
fmt.Fprintf(ios.Out, "Cloning %s/%s to %s...\n", owner, name, destination)
|
||||
|
||||
// Create parent directory if it doesn't exist
|
||||
if dir := filepath.Dir(destination); dir != "." {
|
||||
|
|
@ -250,17 +293,21 @@ func runRepoClone(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
ios.StartSpinner("Cloning repository...")
|
||||
// Execute git clone
|
||||
gitCmd := exec.Command("git", "clone", cloneURL, destination)
|
||||
gitCmd.Stdout = os.Stdout
|
||||
gitCmd.Stderr = os.Stderr
|
||||
gitCmd.Stdin = os.Stdin
|
||||
gitCmd.Stdout = ios.Out
|
||||
gitCmd.Stderr = ios.ErrOut
|
||||
gitCmd.Stdin = ios.In
|
||||
|
||||
if err := gitCmd.Run(); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
fmt.Printf("Repository cloned successfully to %s\n", destination)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Repository cloned successfully to %s\n", cs.SuccessIcon(), destination)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -282,14 +329,17 @@ func runRepoFork(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ios.StartSpinner("Forking repository...")
|
||||
fork, _, err := client.CreateFork(owner, name, gitea.CreateForkOption{})
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fork repository: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Repository forked successfully\n")
|
||||
fmt.Printf("View at: %s\n", fork.HTMLURL)
|
||||
fmt.Printf("Clone URL: %s\n", fork.CloneURL)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Repository forked successfully\n", cs.SuccessIcon())
|
||||
fmt.Fprintf(ios.Out, "View at: %s\n", fork.HTMLURL)
|
||||
fmt.Fprintf(ios.Out, "Clone URL: %s\n", fork.CloneURL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -335,12 +385,14 @@ func runRepoCreate(cmd *cobra.Command, args []string) error {
|
|||
License: license,
|
||||
}
|
||||
|
||||
ios.StartSpinner("Creating repository...")
|
||||
var repo *gitea.Repository
|
||||
if isOrg {
|
||||
repo, _, err = client.CreateOrgRepo(org, opt)
|
||||
} else {
|
||||
repo, _, err = client.CreateRepo(opt)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create repository: %w", err)
|
||||
}
|
||||
|
|
@ -354,7 +406,7 @@ func runRepoCreate(cmd *cobra.Command, args []string) error {
|
|||
} else {
|
||||
user, _, userErr := client.GetMyUserInfo()
|
||||
if userErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: repository created but could not determine owner for homepage: %v\n", userErr)
|
||||
fmt.Fprintf(ios.ErrOut, "warning: repository created but could not determine owner for homepage: %v\n", userErr)
|
||||
homepage = "" // skip EditRepo
|
||||
} else {
|
||||
ownerName = user.UserName
|
||||
|
|
@ -366,23 +418,24 @@ func runRepoCreate(cmd *cobra.Command, args []string) error {
|
|||
Website: &homepage,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: repository created but failed to set homepage: %v\n", err)
|
||||
fmt.Fprintf(ios.ErrOut, "warning: repository created but failed to set homepage: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if team != "" {
|
||||
if !isOrg {
|
||||
fmt.Fprintln(os.Stderr, "warning: --team is only meaningful for organization repositories")
|
||||
fmt.Fprintln(ios.ErrOut, "warning: --team is only meaningful for organization repositories")
|
||||
} else {
|
||||
_, err = client.AddRepoTeam(org, repo.Name, team)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: repository created but failed to add team %q: %v\n", team, err)
|
||||
fmt.Fprintf(ios.ErrOut, "warning: repository created but failed to add team %q: %v\n", team, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Repository created: %s\n", repo.HTMLURL)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Repository created: %s\n", cs.SuccessIcon(), repo.HTMLURL)
|
||||
|
||||
if doClone {
|
||||
cloneURL := repo.CloneURL
|
||||
|
|
@ -391,11 +444,11 @@ func runRepoCreate(cmd *cobra.Command, args []string) error {
|
|||
cloneURL = repo.SSHURL
|
||||
}
|
||||
}
|
||||
fmt.Printf("Cloning into %s...\n", repo.Name)
|
||||
fmt.Fprintf(ios.Out, "Cloning into %s...\n", repo.Name)
|
||||
gitCmd := exec.Command("git", "clone", cloneURL, repo.Name)
|
||||
gitCmd.Stdout = os.Stdout
|
||||
gitCmd.Stderr = os.Stderr
|
||||
gitCmd.Stdin = os.Stdin
|
||||
gitCmd.Stdout = ios.Out
|
||||
gitCmd.Stderr = ios.ErrOut
|
||||
gitCmd.Stdin = ios.In
|
||||
if err := gitCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
|
@ -449,6 +502,11 @@ func runRepoEdit(cmd *cobra.Command, args []string) error {
|
|||
opt := gitea.EditRepoOption{}
|
||||
changed := false
|
||||
|
||||
if cmd.Flags().Changed("name") {
|
||||
n, _ := cmd.Flags().GetString("name")
|
||||
opt.Name = &n
|
||||
changed = true
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
d, _ := cmd.Flags().GetString("description")
|
||||
opt.Description = &d
|
||||
|
|
@ -476,36 +534,84 @@ func runRepoEdit(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if !changed {
|
||||
return fmt.Errorf("no changes specified; use flags like --public, --private, --description, --homepage, or --default-branch")
|
||||
return fmt.Errorf("no changes specified; use flags like --name, --public, --private, --description, --homepage, or --default-branch")
|
||||
}
|
||||
|
||||
ios.StartSpinner("Updating repository...")
|
||||
repository, _, err := client.EditRepo(owner, name, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to edit repository: %w", err)
|
||||
}
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(repository)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repository)
|
||||
}
|
||||
|
||||
fmt.Printf("Repository updated: %s\n", repository.HTMLURL)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Repository updated: %s\n", cs.SuccessIcon(), repository.HTMLURL)
|
||||
if opt.Name != nil {
|
||||
fmt.Fprintf(ios.Out, "Renamed to: %s\n", repository.FullName)
|
||||
}
|
||||
if opt.Private != nil {
|
||||
if *opt.Private {
|
||||
fmt.Println("Visibility: private")
|
||||
fmt.Fprintln(ios.Out, "Visibility: private")
|
||||
} else {
|
||||
fmt.Println("Visibility: public")
|
||||
fmt.Fprintln(ios.Out, "Visibility: public")
|
||||
}
|
||||
}
|
||||
if opt.Description != nil {
|
||||
fmt.Printf("Description: %s\n", *opt.Description)
|
||||
fmt.Fprintf(ios.Out, "Description: %s\n", *opt.Description)
|
||||
}
|
||||
if opt.Website != nil {
|
||||
fmt.Printf("Homepage: %s\n", *opt.Website)
|
||||
fmt.Fprintf(ios.Out, "Homepage: %s\n", *opt.Website)
|
||||
}
|
||||
if opt.DefaultBranch != nil {
|
||||
fmt.Printf("Default branch: %s\n", *opt.DefaultBranch)
|
||||
fmt.Fprintf(ios.Out, "Default branch: %s\n", *opt.DefaultBranch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRepoRename(cmd *cobra.Command, args []string) error {
|
||||
var repo string
|
||||
if r, _ := cmd.Flags().GetString("repo"); r != "" {
|
||||
repo = r
|
||||
}
|
||||
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newName := args[0]
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opt := gitea.EditRepoOption{
|
||||
Name: &newName,
|
||||
}
|
||||
|
||||
ios.StartSpinner("Renaming repository...")
|
||||
repository, _, err := client.EditRepo(owner, name, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rename repository: %w", err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repository)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Renamed %s/%s to %s\n", cs.SuccessIcon(), owner, name, repository.FullName)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue