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.
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"forgejo.zerova.net/sid/fgj-sid/internal/git"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var cfgFile string
|
|
var jsonErrors bool
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Use: "fgj",
|
|
Short: "Forgejo CLI tool - work seamlessly with Forgejo from the command line",
|
|
Long: `fgj is a command line tool for Forgejo instances (including Codeberg).
|
|
It brings pull requests, issues, and other Forgejo concepts to the terminal.`,
|
|
Version: "0.3.0c",
|
|
SilenceErrors: true,
|
|
}
|
|
|
|
// JSONErrors reports whether the --json-errors flag is set.
|
|
func JSONErrors() bool {
|
|
return jsonErrors
|
|
}
|
|
|
|
func Execute() error {
|
|
return rootCmd.Execute()
|
|
}
|
|
|
|
func init() {
|
|
cobra.OnInitialize(initConfig)
|
|
|
|
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/fgj/config.yaml)")
|
|
rootCmd.PersistentFlags().BoolVar(&jsonErrors, "json-errors", false, "output errors as structured JSON to stderr")
|
|
rootCmd.PersistentFlags().String("hostname", "", "Forgejo instance hostname")
|
|
_ = viper.BindPFlag("hostname", rootCmd.PersistentFlags().Lookup("hostname"))
|
|
}
|
|
|
|
func initConfig() {
|
|
if cfgFile != "" {
|
|
viper.SetConfigFile(cfgFile)
|
|
} else {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
fmt.Fprintln(ios.ErrOut, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
configDir := home + "/.config/fgj"
|
|
_ = os.MkdirAll(configDir, 0755)
|
|
|
|
viper.AddConfigPath(configDir)
|
|
viper.SetConfigType("yaml")
|
|
viper.SetConfigName("config")
|
|
}
|
|
|
|
viper.AutomaticEnv()
|
|
viper.SetEnvPrefix("FGJ")
|
|
|
|
_ = viper.ReadInConfig()
|
|
}
|
|
|
|
// parseRepo parses the repository string in the format "owner/name".
|
|
// If not provided, it attempts to auto-detect from the git repository.
|
|
func parseRepo(repo string) (string, string, error) {
|
|
// If repo flag is provided, use it
|
|
if repo != "" {
|
|
parts := strings.Split(repo, "/")
|
|
if len(parts) != 2 {
|
|
return "", "", fmt.Errorf("invalid repository format: %s (expected: owner/name)", repo)
|
|
}
|
|
return parts[0], parts[1], nil
|
|
}
|
|
|
|
// Try to auto-detect from git
|
|
owner, name, err := git.DetectRepo()
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("repository flag is required (use -R owner/name) or run from a git repository: %w", err)
|
|
}
|
|
|
|
return owner, name, nil
|
|
}
|
|
|
|
// getDetectedHost attempts to auto-detect the Forgejo instance hostname.
|
|
// Returns empty string if detection fails, which will fall back to other methods.
|
|
func getDetectedHost() string {
|
|
host, err := git.DetectHost()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return host
|
|
}
|
|
|
|
// promptLine prints a prompt to stderr and reads a line from stdin.
|
|
func promptLine(prompt string) (string, error) {
|
|
fmt.Fprint(ios.ErrOut, prompt)
|
|
var buf [1024]byte
|
|
n, err := ios.In.Read(buf[:])
|
|
if err != nil {
|
|
return "", fmt.Errorf("reading input: %w", err)
|
|
}
|
|
return strings.TrimSpace(string(buf[:n])), nil
|
|
}
|
|
|
|
// parseIssueArg parses an issue/PR number from various formats:
|
|
// "123", "#123", "https://host/owner/repo/pulls/123", "https://host/owner/repo/issues/123"
|
|
func parseIssueArg(arg string) (int64, error) {
|
|
arg = strings.TrimPrefix(arg, "#")
|
|
// Try URL format
|
|
if strings.HasPrefix(arg, "http") {
|
|
parts := strings.Split(strings.TrimRight(arg, "/"), "/")
|
|
arg = parts[len(parts)-1]
|
|
}
|
|
return strconv.ParseInt(arg, 10, 64)
|
|
}
|