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
117
cmd/wiki.go
117
cmd/wiki.go
|
|
@ -5,29 +5,28 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Wiki API response types
|
||||
|
||||
type wikiPageMeta struct {
|
||||
Title string `json:"title"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
SubURL string `json:"sub_url"`
|
||||
Title string `json:"title"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
SubURL string `json:"sub_url"`
|
||||
LastCommit *wikiCommit `json:"last_commit"`
|
||||
}
|
||||
|
||||
type wikiCommit struct {
|
||||
ID string `json:"id"`
|
||||
Author *wikiUser `json:"author"`
|
||||
Committer *wikiUser `json:"committer"`
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
Author *wikiUser `json:"author"`
|
||||
Committer *wikiUser `json:"committer"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type wikiUser struct {
|
||||
|
|
@ -79,6 +78,9 @@ var wikiViewCmd = &cobra.Command{
|
|||
Example: ` # View a wiki page
|
||||
fgj wiki view Home
|
||||
|
||||
# Open in browser
|
||||
fgj wiki view Home --web
|
||||
|
||||
# View a wiki page as JSON (includes content)
|
||||
fgj wiki view Home --json
|
||||
|
||||
|
|
@ -133,6 +135,9 @@ var wikiDeleteCmd = &cobra.Command{
|
|||
Example: ` # Delete a wiki page
|
||||
fgj wiki delete "Old Page"
|
||||
|
||||
# Delete without confirmation
|
||||
fgj wiki delete "Old Page" -y
|
||||
|
||||
# Delete a wiki page from a specific repo
|
||||
fgj wiki delete "Outdated Guide" -R owner/repo`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
|
@ -148,22 +153,24 @@ func init() {
|
|||
wikiCmd.AddCommand(wikiDeleteCmd)
|
||||
|
||||
wikiListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiListCmd.Flags().Bool("json", false, "Output as JSON")
|
||||
addJSONFlags(wikiListCmd, "Output as JSON")
|
||||
|
||||
wikiViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiViewCmd.Flags().Bool("json", false, "Output as JSON")
|
||||
addJSONFlags(wikiViewCmd, "Output as JSON")
|
||||
wikiViewCmd.Flags().BoolP("web", "w", false, "Open in web browser")
|
||||
|
||||
wikiCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiCreateCmd.Flags().StringP("body", "b", "", "Wiki page content")
|
||||
wikiCreateCmd.Flags().String("body-file", "", "Read content from file (use \"-\" for stdin)")
|
||||
wikiCreateCmd.Flags().Bool("json", false, "Output created page as JSON")
|
||||
addJSONFlags(wikiCreateCmd, "Output created page as JSON")
|
||||
|
||||
wikiEditCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiEditCmd.Flags().StringP("body", "b", "", "Wiki page content")
|
||||
wikiEditCmd.Flags().String("body-file", "", "Read content from file (use \"-\" for stdin)")
|
||||
wikiEditCmd.Flags().Bool("json", false, "Output updated page as JSON")
|
||||
addJSONFlags(wikiEditCmd, "Output updated page as JSON")
|
||||
|
||||
wikiDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
|
||||
}
|
||||
|
||||
func newWikiClient(cmd *cobra.Command) (*api.Client, string, string, error) {
|
||||
|
|
@ -194,37 +201,38 @@ func runWikiList(cmd *cobra.Command, args []string) error {
|
|||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", url.PathEscape(owner), url.PathEscape(name))
|
||||
|
||||
ios.StartSpinner("Fetching wiki pages...")
|
||||
var pages []wikiPageMeta
|
||||
if err := client.GetJSON(path, &pages); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to list wiki pages: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(pages)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, pages)
|
||||
}
|
||||
|
||||
if len(pages) == 0 {
|
||||
fmt.Println("No wiki pages found")
|
||||
fmt.Fprintln(ios.Out, "No wiki pages found")
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "TITLE\tLAST UPDATED\n")
|
||||
isTTY := ios.IsStdoutTTY()
|
||||
tp := ios.NewTablePrinter()
|
||||
tp.AddHeader("TITLE", "LAST UPDATED")
|
||||
for _, p := range pages {
|
||||
updated := ""
|
||||
if p.LastCommit != nil && p.LastCommit.Committer != nil && p.LastCommit.Committer.Date != "" {
|
||||
if t, err := time.Parse(time.RFC3339, p.LastCommit.Committer.Date); err == nil {
|
||||
updated = t.Format("2006-01-02 15:04:05")
|
||||
updated = text.FormatDate(t, isTTY)
|
||||
} else {
|
||||
updated = p.LastCommit.Committer.Date
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", p.Title, updated)
|
||||
tp.AddRow(p.Title, updated)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
return nil
|
||||
return tp.Render()
|
||||
}
|
||||
|
||||
func runWikiView(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -238,27 +246,42 @@ func runWikiView(cmd *cobra.Command, args []string) error {
|
|||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s",
|
||||
url.PathEscape(owner), url.PathEscape(name), url.PathEscape(title))
|
||||
|
||||
ios.StartSpinner("Fetching wiki page...")
|
||||
var page wikiPage
|
||||
if err := client.GetJSON(path, &page); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to get wiki page: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(page.ContentBase64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode wiki page content: %w", err)
|
||||
}
|
||||
|
||||
if web, _ := cmd.Flags().GetBool("web"); web {
|
||||
if page.HTMLURL != "" {
|
||||
return ios.OpenInBrowser(page.HTMLURL)
|
||||
}
|
||||
return fmt.Errorf("wiki page has no HTML URL")
|
||||
}
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
page.Content = string(content)
|
||||
return writeJSON(page)
|
||||
}
|
||||
|
||||
fmt.Printf("# %s\n\n", page.Title)
|
||||
fmt.Print(string(content))
|
||||
if err := ios.StartPager(); err != nil {
|
||||
fmt.Fprintf(ios.ErrOut, "warning: failed to start pager: %v\n", err)
|
||||
}
|
||||
defer ios.StopPager()
|
||||
|
||||
fmt.Fprintf(ios.Out, "# %s\n\n", page.Title)
|
||||
fmt.Fprint(ios.Out, string(content))
|
||||
// Ensure trailing newline
|
||||
if len(content) > 0 && content[len(content)-1] != '\n' {
|
||||
fmt.Println()
|
||||
fmt.Fprintln(ios.Out)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -288,17 +311,20 @@ func runWikiCreate(cmd *cobra.Command, args []string) error {
|
|||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(body)),
|
||||
}
|
||||
|
||||
ios.StartSpinner("Creating wiki page...")
|
||||
var page wikiPage
|
||||
if _, err := client.DoJSON(http.MethodPost, path, reqBody, &page); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to create wiki page: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(page)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, page)
|
||||
}
|
||||
|
||||
fmt.Printf("Wiki page created: %s\n", title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Wiki page created: %s\n", cs.SuccessIcon(), title)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -326,35 +352,54 @@ func runWikiEdit(cmd *cobra.Command, args []string) error {
|
|||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(body)),
|
||||
}
|
||||
|
||||
ios.StartSpinner("Updating wiki page...")
|
||||
var page wikiPage
|
||||
if _, err := client.DoJSON(http.MethodPatch, path, reqBody, &page); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to update wiki page: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(page)
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, page)
|
||||
}
|
||||
|
||||
fmt.Printf("Wiki page updated: %s\n", title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Wiki page updated: %s\n", cs.SuccessIcon(), title)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWikiDelete(cmd *cobra.Command, args []string) error {
|
||||
title := args[0]
|
||||
yes, _ := cmd.Flags().GetBool("yes")
|
||||
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !yes {
|
||||
confirmed, err := ios.ConfirmAction(fmt.Sprintf("Delete wiki page %q?", title))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Fprintln(ios.ErrOut, "Aborted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s",
|
||||
url.PathEscape(owner), url.PathEscape(name), url.PathEscape(title))
|
||||
|
||||
ios.StartSpinner("Deleting wiki page...")
|
||||
if _, err := client.DoJSON(http.MethodDelete, path, nil, nil); err != nil {
|
||||
ios.StopSpinner()
|
||||
return fmt.Errorf("failed to delete wiki page: %w", err)
|
||||
}
|
||||
ios.StopSpinner()
|
||||
|
||||
fmt.Printf("Wiki page deleted: %s\n", title)
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Wiki page deleted: %s\n", cs.SuccessIcon(), title)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue