feat: v0.3.0c — add labels, milestones, wiki, issue dependencies
New commands: - fgj label list/create/edit/delete - fgj milestone list/view/create/edit/delete - fgj wiki list/view/create/edit/delete Enhanced: - fgj issue edit --add-dependency/--remove-dependency
This commit is contained in:
parent
7ee5a61910
commit
95da06c003
7 changed files with 1206 additions and 3 deletions
360
cmd/wiki.go
Normal file
360
cmd/wiki.go
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"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"
|
||||
"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"`
|
||||
LastCommit *wikiCommit `json:"last_commit"`
|
||||
}
|
||||
|
||||
type wikiCommit struct {
|
||||
ID string `json:"id"`
|
||||
Author *wikiUser `json:"author"`
|
||||
Committer *wikiUser `json:"committer"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type wikiUser struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type wikiPage struct {
|
||||
Title string `json:"title"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
SubURL string `json:"sub_url"`
|
||||
ContentBase64 string `json:"content_base64"`
|
||||
LastCommit *wikiCommit `json:"last_commit"`
|
||||
// Decoded content for JSON output
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type wikiCreateRequest struct {
|
||||
Title string `json:"title"`
|
||||
ContentBase64 string `json:"content_base64"`
|
||||
}
|
||||
|
||||
var wikiCmd = &cobra.Command{
|
||||
Use: "wiki",
|
||||
Short: "Manage repository wiki pages",
|
||||
Long: "View and manage wiki pages for a repository.",
|
||||
}
|
||||
|
||||
var wikiListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List wiki pages",
|
||||
Long: "List all wiki pages for a repository.",
|
||||
Example: ` # List wiki pages for the current repo
|
||||
fgj wiki list
|
||||
|
||||
# List wiki pages for a specific repo
|
||||
fgj wiki list -R owner/repo
|
||||
|
||||
# Output as JSON
|
||||
fgj wiki list --json`,
|
||||
RunE: runWikiList,
|
||||
}
|
||||
|
||||
var wikiViewCmd = &cobra.Command{
|
||||
Use: "view <title>",
|
||||
Short: "View a wiki page",
|
||||
Long: "Display the content of a wiki page.",
|
||||
Example: ` # View a wiki page
|
||||
fgj wiki view Home
|
||||
|
||||
# View a wiki page as JSON (includes content)
|
||||
fgj wiki view Home --json
|
||||
|
||||
# View a wiki page from a specific repo
|
||||
fgj wiki view "Getting-Started" -R owner/repo`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runWikiView,
|
||||
}
|
||||
|
||||
var wikiCreateCmd = &cobra.Command{
|
||||
Use: "create <title>",
|
||||
Short: "Create a wiki page",
|
||||
Long: "Create a new wiki page in the repository.",
|
||||
Example: ` # Create a wiki page with inline content
|
||||
fgj wiki create "Getting Started" -b "# Welcome\nThis is the getting started guide."
|
||||
|
||||
# Create a wiki page from a file
|
||||
fgj wiki create "Setup Guide" --body-file setup.md
|
||||
|
||||
# Create a wiki page from stdin
|
||||
echo "# FAQ" | fgj wiki create FAQ --body-file -
|
||||
|
||||
# Output as JSON
|
||||
fgj wiki create "New Page" -b "Content here" --json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runWikiCreate,
|
||||
}
|
||||
|
||||
var wikiEditCmd = &cobra.Command{
|
||||
Use: "edit <title>",
|
||||
Short: "Edit a wiki page",
|
||||
Long: "Edit an existing wiki page in the repository.",
|
||||
Example: ` # Edit a wiki page with new content
|
||||
fgj wiki edit Home -b "# Updated Home\nNew content here."
|
||||
|
||||
# Edit a wiki page from a file
|
||||
fgj wiki edit "Setup Guide" --body-file updated-setup.md
|
||||
|
||||
# Edit a wiki page from stdin
|
||||
cat new-content.md | fgj wiki edit Home --body-file -
|
||||
|
||||
# Output as JSON
|
||||
fgj wiki edit Home -b "Updated content" --json`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runWikiEdit,
|
||||
}
|
||||
|
||||
var wikiDeleteCmd = &cobra.Command{
|
||||
Use: "delete <title>",
|
||||
Short: "Delete a wiki page",
|
||||
Long: "Delete a wiki page from the repository.",
|
||||
Example: ` # Delete a wiki page
|
||||
fgj wiki delete "Old Page"
|
||||
|
||||
# Delete a wiki page from a specific repo
|
||||
fgj wiki delete "Outdated Guide" -R owner/repo`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runWikiDelete,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(wikiCmd)
|
||||
wikiCmd.AddCommand(wikiListCmd)
|
||||
wikiCmd.AddCommand(wikiViewCmd)
|
||||
wikiCmd.AddCommand(wikiCreateCmd)
|
||||
wikiCmd.AddCommand(wikiEditCmd)
|
||||
wikiCmd.AddCommand(wikiDeleteCmd)
|
||||
|
||||
wikiListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiListCmd.Flags().Bool("json", false, "Output as JSON")
|
||||
|
||||
wikiViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
wikiViewCmd.Flags().Bool("json", false, "Output as JSON")
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
wikiDeleteCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
|
||||
}
|
||||
|
||||
func newWikiClient(cmd *cobra.Command) (*api.Client, string, string, error) {
|
||||
repo, _ := cmd.Flags().GetString("repo")
|
||||
owner, name, err := parseRepo(repo)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost())
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
return client, owner, name, nil
|
||||
}
|
||||
|
||||
func runWikiList(cmd *cobra.Command, args []string) error {
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", url.PathEscape(owner), url.PathEscape(name))
|
||||
|
||||
var pages []wikiPageMeta
|
||||
if err := client.GetJSON(path, &pages); err != nil {
|
||||
return fmt.Errorf("failed to list wiki pages: %w", err)
|
||||
}
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(pages)
|
||||
}
|
||||
|
||||
if len(pages) == 0 {
|
||||
fmt.Println("No wiki pages found")
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "TITLE\tLAST UPDATED\n")
|
||||
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")
|
||||
} else {
|
||||
updated = p.LastCommit.Committer.Date
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", p.Title, updated)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWikiView(cmd *cobra.Command, args []string) error {
|
||||
title := args[0]
|
||||
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s",
|
||||
url.PathEscape(owner), url.PathEscape(name), url.PathEscape(title))
|
||||
|
||||
var page wikiPage
|
||||
if err := client.GetJSON(path, &page); err != nil {
|
||||
return fmt.Errorf("failed to get wiki page: %w", err)
|
||||
}
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(page.ContentBase64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode wiki page content: %w", err)
|
||||
}
|
||||
|
||||
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))
|
||||
// Ensure trailing newline
|
||||
if len(content) > 0 && content[len(content)-1] != '\n' {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWikiCreate(cmd *cobra.Command, args []string) error {
|
||||
title := args[0]
|
||||
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := readBody(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if body == "" {
|
||||
return fmt.Errorf("content is required (use --body or --body-file)")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new",
|
||||
url.PathEscape(owner), url.PathEscape(name))
|
||||
|
||||
reqBody := wikiCreateRequest{
|
||||
Title: title,
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(body)),
|
||||
}
|
||||
|
||||
var page wikiPage
|
||||
if _, err := client.DoJSON(http.MethodPost, path, reqBody, &page); err != nil {
|
||||
return fmt.Errorf("failed to create wiki page: %w", err)
|
||||
}
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(page)
|
||||
}
|
||||
|
||||
fmt.Printf("Wiki page created: %s\n", title)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWikiEdit(cmd *cobra.Command, args []string) error {
|
||||
title := args[0]
|
||||
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := readBody(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if body == "" {
|
||||
return fmt.Errorf("content is required (use --body or --body-file)")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s",
|
||||
url.PathEscape(owner), url.PathEscape(name), url.PathEscape(title))
|
||||
|
||||
reqBody := wikiCreateRequest{
|
||||
Title: title,
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte(body)),
|
||||
}
|
||||
|
||||
var page wikiPage
|
||||
if _, err := client.DoJSON(http.MethodPatch, path, reqBody, &page); err != nil {
|
||||
return fmt.Errorf("failed to update wiki page: %w", err)
|
||||
}
|
||||
|
||||
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||
if jsonFlag {
|
||||
return writeJSON(page)
|
||||
}
|
||||
|
||||
fmt.Printf("Wiki page updated: %s\n", title)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runWikiDelete(cmd *cobra.Command, args []string) error {
|
||||
title := args[0]
|
||||
|
||||
client, owner, name, err := newWikiClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s",
|
||||
url.PathEscape(owner), url.PathEscape(name), url.PathEscape(title))
|
||||
|
||||
if _, err := client.DoJSON(http.MethodDelete, path, nil, nil); err != nil {
|
||||
return fmt.Errorf("failed to delete wiki page: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Wiki page deleted: %s\n", title)
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue