- fgj logins {list,default}: complementary UI to 'fgj auth'. 'list'
shows all configured hosts (hostname, user, protocol, default flag,
match_dirs) with --json. 'default [hostname]' gets or sets which
host wins in resolution when no other signal is present.
Adds 'Default bool' field to HostConfig; GetHost consults it between
match_dirs and the codeberg.org fallback. Multiple defaults tolerated
with a stderr warning; alphabetical-first wins.
- fgj actions run delete: delete a completed workflow run via raw
DELETE (SDK v0.23.2 has no DeleteRepoActionRun). Fetches run first
and refuses to delete non-terminal states unless --force; suggests
'actions run cancel' for those. Confirmation prompt unless --yes.
- pr list / issue list gain --since and --before date filter flags.
Accepts YYYY-MM-DD, RFC 3339, YYYY-MM-DD HH:MM:SS, and relative
deltas (7d, 24h, 2w, 1m — months=30 days). Issues use server-side
filter via ListIssueOption.Since/Before; PRs fall back to client-side
(SDK lacks Since/Before on ListPullRequestsOptions).
- fgj label update added as alias for 'fgj label edit' (tea-compat).
All changes:
cmd/logins.go (new, 140 LOC)
cmd/actions_run_delete.go (new, ~85 LOC)
cmd/pr.go, cmd/issue.go (+parseDateArg helper, filter wiring)
cmd/label.go (1-line alias)
internal/config/config.go (Default field + DefaultHost method)
CHANGELOG.md
Built in parallel by three sub-agents; plus the label alias done
serially. go build / go vet / go test -race all clean.
160 lines
4 KiB
Go
160 lines
4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var loginsCmd = &cobra.Command{
|
|
Use: "logins",
|
|
Short: "Manage configured Forgejo/Gitea logins",
|
|
Long: `Manage configured Forgejo/Gitea logins.
|
|
|
|
This is a complementary command to 'fgj auth' using the noun vocabulary
|
|
familiar to users of tea. Use 'fgj auth login' to add a new login and
|
|
'fgj auth logout' to remove one.`,
|
|
}
|
|
|
|
var loginsListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Aliases: []string{"ls"},
|
|
Short: "List all configured logins",
|
|
Long: "List all configured Forgejo/Gitea logins with their hostname, user, protocol, default flag, and match_dirs.",
|
|
RunE: runLoginsList,
|
|
}
|
|
|
|
var loginsDefaultCmd = &cobra.Command{
|
|
Use: "default [hostname]",
|
|
Short: "Get or set the default login",
|
|
Long: `Get or set the default login.
|
|
|
|
With no argument, prints the currently-configured default hostname
|
|
(or "no default set" if none is configured).
|
|
|
|
With a hostname argument, marks that login as the default and unsets
|
|
the default flag on all other logins.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: runLoginsDefault,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(loginsCmd)
|
|
loginsCmd.AddCommand(loginsListCmd)
|
|
loginsCmd.AddCommand(loginsDefaultCmd)
|
|
|
|
addJSONFlags(loginsListCmd, "Output logins as JSON")
|
|
}
|
|
|
|
// loginEntry is the JSON representation of a configured login.
|
|
type loginEntry struct {
|
|
Hostname string `json:"hostname"`
|
|
User string `json:"user"`
|
|
GitProtocol string `json:"git_protocol"`
|
|
Default bool `json:"default"`
|
|
MatchDirs []string `json:"match_dirs"`
|
|
}
|
|
|
|
func runLoginsList(cmd *cobra.Command, args []string) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
hostnames := make([]string, 0, len(cfg.Hosts))
|
|
for hostname := range cfg.Hosts {
|
|
hostnames = append(hostnames, hostname)
|
|
}
|
|
|
|
// Sort by config-file order; fall back to alphabetical when Order is
|
|
// equal (e.g., both zero because the config was constructed in-memory
|
|
// rather than loaded from disk).
|
|
sort.Slice(hostnames, func(i, j int) bool {
|
|
a, b := cfg.Hosts[hostnames[i]], cfg.Hosts[hostnames[j]]
|
|
if a.Order != b.Order {
|
|
return a.Order < b.Order
|
|
}
|
|
return hostnames[i] < hostnames[j]
|
|
})
|
|
|
|
if wantJSON(cmd) {
|
|
entries := make([]loginEntry, 0, len(hostnames))
|
|
for _, hostname := range hostnames {
|
|
h := cfg.Hosts[hostname]
|
|
dirs := h.MatchDirs
|
|
if dirs == nil {
|
|
dirs = []string{}
|
|
}
|
|
entries = append(entries, loginEntry{
|
|
Hostname: hostname,
|
|
User: h.User,
|
|
GitProtocol: h.GitProtocol,
|
|
Default: h.Default,
|
|
MatchDirs: dirs,
|
|
})
|
|
}
|
|
return outputJSON(cmd, entries)
|
|
}
|
|
|
|
if len(hostnames) == 0 {
|
|
fmt.Fprintln(ios.Out, "No logins configured")
|
|
fmt.Fprintln(ios.Out, "Run 'fgj auth login' to add a login")
|
|
return nil
|
|
}
|
|
|
|
tp := ios.NewTablePrinter()
|
|
tp.AddHeader("HOSTNAME", "USER", "PROTOCOL", "DEFAULT", "MATCH_DIRS")
|
|
for _, hostname := range hostnames {
|
|
h := cfg.Hosts[hostname]
|
|
defaultMark := ""
|
|
if h.Default {
|
|
defaultMark = "*"
|
|
}
|
|
tp.AddRow(
|
|
hostname,
|
|
h.User,
|
|
h.GitProtocol,
|
|
defaultMark,
|
|
strings.Join(h.MatchDirs, ", "),
|
|
)
|
|
}
|
|
return tp.Render()
|
|
}
|
|
|
|
func runLoginsDefault(cmd *cobra.Command, args []string) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
current := cfg.DefaultHost()
|
|
if current == "" {
|
|
fmt.Fprintln(ios.Out, "no default set")
|
|
return nil
|
|
}
|
|
fmt.Fprintln(ios.Out, current)
|
|
return nil
|
|
}
|
|
|
|
target := args[0]
|
|
if _, ok := cfg.Hosts[target]; !ok {
|
|
return fmt.Errorf("no configuration found for host %s", target)
|
|
}
|
|
|
|
for hostname, h := range cfg.Hosts {
|
|
h.Default = (hostname == target)
|
|
cfg.Hosts[hostname] = h
|
|
}
|
|
|
|
if err := cfg.Save(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
|
|
cs := ios.ColorScheme()
|
|
fmt.Fprintf(ios.Out, "%s Default host set to %s\n", cs.SuccessIcon(), target)
|
|
return nil
|
|
}
|