forked from Gitlink/gitlink-cli
684 lines
19 KiB
Go
684 lines
19 KiB
Go
package wiki
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gitlink-org/gitlink-cli/internal/auth"
|
|
"github.com/gitlink-org/gitlink-cli/internal/client"
|
|
"github.com/gitlink-org/gitlink-cli/internal/output"
|
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
|
)
|
|
|
|
const gatewayBaseURL = "https://gateway.gitlink.org.cn/api"
|
|
|
|
var (
|
|
projectIDCache sync.Map
|
|
gatewayClient *client.Client
|
|
gatewayOnce sync.Once
|
|
)
|
|
|
|
func wikiPath(endpoint string) string {
|
|
return "/wiki/open/" + endpoint
|
|
}
|
|
|
|
func getGatewayClient() *client.Client {
|
|
gatewayOnce.Do(func() {
|
|
gatewayClient = &client.Client{
|
|
HTTP: auth.NewHTTPClient(),
|
|
BaseURL: gatewayBaseURL,
|
|
SkipJSONSuffix: true,
|
|
}
|
|
})
|
|
return gatewayClient
|
|
}
|
|
|
|
func callWikiAPI(ctx *common.RuntimeContext, method, path string, body interface{}) (*output.Envelope, error) {
|
|
gc := getGatewayClient()
|
|
gc.Debug = ctx.Client.Debug
|
|
env, err := gc.Do(method, path, body, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return unwrapGatewayResponse(env)
|
|
}
|
|
|
|
func callWikiAPIWithQuery(ctx *common.RuntimeContext, method, path string, query url.Values) (*output.Envelope, error) {
|
|
gc := getGatewayClient()
|
|
gc.Debug = ctx.Client.Debug
|
|
env, err := gc.Do(method, path, nil, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return unwrapGatewayResponse(env)
|
|
}
|
|
|
|
func unwrapGatewayResponse(env *output.Envelope) (*output.Envelope, error) {
|
|
resp, ok := env.Data.(map[string]interface{})
|
|
if !ok {
|
|
return env, nil
|
|
}
|
|
if code, ok := resp["code"]; ok {
|
|
switch v := code.(type) {
|
|
case float64:
|
|
if v != 200 && v != 201 {
|
|
msg, _ := resp["msg"].(string)
|
|
return nil, fmt.Errorf("[%.0f] %s", v, msg)
|
|
}
|
|
}
|
|
}
|
|
if innerData, ok := resp["data"]; ok {
|
|
return output.SuccessEnvelope(innerData, env.Meta), nil
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
func resolveProjectID(ctx *common.RuntimeContext) (string, error) {
|
|
key := ctx.Owner + "/" + ctx.Repo
|
|
if cached, ok := projectIDCache.Load(key); ok {
|
|
return cached.(string), nil
|
|
}
|
|
|
|
path := fmt.Sprintf("/%s/%s/detail", ctx.Owner, ctx.Repo)
|
|
env, err := ctx.CallAPI("GET", path, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to fetch project details (needed for projectId): %w", err)
|
|
}
|
|
|
|
data, ok := env.Data.(map[string]interface{})
|
|
if !ok {
|
|
return "", fmt.Errorf("unexpected response from project detail API")
|
|
}
|
|
|
|
pid, ok := data["project_id"]
|
|
if !ok {
|
|
return "", fmt.Errorf("project_id not found in project detail response")
|
|
}
|
|
|
|
var pidStr string
|
|
switch v := pid.(type) {
|
|
case float64:
|
|
pidStr = fmt.Sprintf("%.0f", v)
|
|
case int:
|
|
pidStr = fmt.Sprintf("%d", v)
|
|
default:
|
|
pidStr = fmt.Sprintf("%v", v)
|
|
}
|
|
|
|
projectIDCache.Store(key, pidStr)
|
|
return pidStr, nil
|
|
}
|
|
|
|
func parseProjectIDInt(pid string) int {
|
|
n, _ := strconv.Atoi(pid)
|
|
return n
|
|
}
|
|
|
|
func resolveUpdateContent(ctx *common.RuntimeContext, text, filePath string) (string, error) {
|
|
if text != "" {
|
|
return text, nil
|
|
}
|
|
if filePath != "" {
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read file %s: %w", filePath, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
return "", fmt.Errorf("no content provided")
|
|
}
|
|
|
|
func fetchPageContent(ctx *common.RuntimeContext, projectID, pageName string) (string, error) {
|
|
q := url.Values{}
|
|
q.Set("owner", ctx.Owner)
|
|
q.Set("repo", ctx.Repo)
|
|
q.Set("projectId", projectID)
|
|
q.Set("pageName", pageName)
|
|
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("getWiki"), q)
|
|
if err != nil {
|
|
return "", fmt.Errorf("获取 Wiki 页面现有内容失败: %w", err)
|
|
}
|
|
data, ok := env.Data.(map[string]interface{})
|
|
if !ok {
|
|
return "", fmt.Errorf("unexpected response from getWiki")
|
|
}
|
|
b64, _ := data["content_base64"].(string)
|
|
if b64 == "" {
|
|
return "", nil
|
|
}
|
|
decoded, err := base64.StdEncoding.DecodeString(b64)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to decode page content: %w", err)
|
|
}
|
|
return string(decoded), nil
|
|
}
|
|
|
|
func resolveContent(ctx *common.RuntimeContext) (string, error) {
|
|
if content := ctx.Arg("content"); content != "" {
|
|
return content, nil
|
|
}
|
|
if filePath := ctx.Arg("file"); filePath != "" {
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read file %s: %w", filePath, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
return "", fmt.Errorf("--content or --file is required to provide wiki page content")
|
|
}
|
|
|
|
func cleanWikiList(env *output.Envelope) {
|
|
items, ok := env.Data.([]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
for _, item := range items {
|
|
m, ok := item.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
delete(m, "wiki_clone_link")
|
|
if raw, ok := m["sub_url"].(string); ok {
|
|
if decoded, err := url.QueryUnescape(raw); err == nil {
|
|
m["sub_url"] = decoded
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func outputWithDecodedContent(ctx *common.RuntimeContext, env *output.Envelope) error {
|
|
data, ok := env.Data.(map[string]interface{})
|
|
if !ok {
|
|
return ctx.Output(env)
|
|
}
|
|
if b64, ok := data["content_base64"].(string); ok && b64 != "" {
|
|
decoded, err := base64.StdEncoding.DecodeString(b64)
|
|
if err == nil {
|
|
data["content_decoded"] = string(decoded)
|
|
}
|
|
}
|
|
return ctx.Output(env)
|
|
}
|
|
|
|
// --- lint types and implementation ---
|
|
|
|
type LintIssue struct {
|
|
Page string `json:"page"`
|
|
Level string `json:"level"`
|
|
Check string `json:"check"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type LintSummary struct {
|
|
Repository string `json:"repository"`
|
|
TotalPages int `json:"total_pages"`
|
|
TotalIssues int `json:"total_issues"`
|
|
Errors int `json:"errors"`
|
|
Warnings int `json:"warnings"`
|
|
Results []LintIssue `json:"results"`
|
|
}
|
|
|
|
var (
|
|
mdLinkRe = regexp.MustCompile(`\[([^\]]*)\]\(([^)]+)\)`)
|
|
imageLinkRe = regexp.MustCompile(`!\[([^\]]*)\]\((https?://[^)]+)\)`)
|
|
)
|
|
|
|
func isCheckEnabled(checkFilter, name string) bool {
|
|
if checkFilter == "" {
|
|
return true
|
|
}
|
|
for _, c := range strings.Split(checkFilter, ",") {
|
|
if strings.TrimSpace(c) == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func checkEmpty(page, content string) []LintIssue {
|
|
if strings.TrimSpace(content) == "" {
|
|
return []LintIssue{{Page: page, Level: "error", Check: "empty", Message: "page is empty"}}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkHeading(page, content string) []LintIssue {
|
|
if content != "" && !strings.HasPrefix(strings.TrimSpace(content), "# ") {
|
|
return []LintIssue{{Page: page, Level: "warning", Check: "headings", Message: "missing H1 heading"}}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkShort(page, content string) []LintIssue {
|
|
if content != "" && len(content) < 50 {
|
|
return []LintIssue{{Page: page, Level: "warning", Check: "short", Message: fmt.Sprintf("content too short (%d chars)", len(content))}}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkDeadLinks(page, content string, knownTitles map[string]bool) []LintIssue {
|
|
var issues []LintIssue
|
|
for _, m := range mdLinkRe.FindAllStringSubmatch(content, -1) {
|
|
if len(m) < 3 {
|
|
continue
|
|
}
|
|
target := m[2]
|
|
// skip external links and anchors
|
|
if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") || strings.HasPrefix(target, "#") {
|
|
continue
|
|
}
|
|
decoded, _ := url.PathUnescape(target)
|
|
if decoded == "" {
|
|
decoded = target
|
|
}
|
|
if !knownTitles[decoded] && !knownTitles[target] {
|
|
issues = append(issues, LintIssue{
|
|
Page: page,
|
|
Level: "error",
|
|
Check: "links",
|
|
Message: fmt.Sprintf("dead link: [%s](%s) -> page %q not found", m[1], target, decoded),
|
|
})
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
func checkImages(page, content string, httpClient *http.Client) []LintIssue {
|
|
var issues []LintIssue
|
|
for _, m := range imageLinkRe.FindAllStringSubmatch(content, -1) {
|
|
if len(m) < 3 {
|
|
continue
|
|
}
|
|
imgURL := m[2]
|
|
req, err := http.NewRequest("HEAD", imgURL, nil)
|
|
if err != nil {
|
|
issues = append(issues, LintIssue{Page: page, Level: "warning", Check: "images", Message: fmt.Sprintf("broken image: %s -> invalid URL", imgURL)})
|
|
continue
|
|
}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
issues = append(issues, LintIssue{Page: page, Level: "warning", Check: "images", Message: fmt.Sprintf("broken image: %s -> unreachable", imgURL)})
|
|
continue
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode >= 400 {
|
|
issues = append(issues, LintIssue{Page: page, Level: "warning", Check: "images", Message: fmt.Sprintf("broken image: %s -> %d", imgURL, resp.StatusCode)})
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
func runLint(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
|
|
checkFilter := ctx.Arg("check")
|
|
fmt.Fprintf(os.Stderr, "Linting wiki pages for %s/%s...\n\n", ctx.Owner, ctx.Repo)
|
|
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Fetch page list
|
|
q := url.Values{}
|
|
q.Set("owner", ctx.Owner)
|
|
q.Set("repo", ctx.Repo)
|
|
q.Set("projectId", projectID)
|
|
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("wikiPages"), q)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list wiki pages: %w", err)
|
|
}
|
|
|
|
// Parse pages: title for display/link-check, sub_url for fetching
|
|
type pageInfo struct {
|
|
title string
|
|
subURL string
|
|
}
|
|
knownTitles := make(map[string]bool)
|
|
var pages []pageInfo
|
|
if items, ok := env.Data.([]interface{}); ok {
|
|
for _, item := range items {
|
|
m, ok := item.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
title, _ := m["title"].(string)
|
|
subURL, _ := m["sub_url"].(string)
|
|
if title != "" {
|
|
knownTitles[title] = true
|
|
if subURL == "" {
|
|
subURL = title
|
|
}
|
|
pages = append(pages, pageInfo{title: title, subURL: subURL})
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(pages) == 0 {
|
|
fmt.Fprintln(os.Stderr, "No wiki pages found.")
|
|
return ctx.OutputData(LintSummary{
|
|
Repository: ctx.Owner + "/" + ctx.Repo,
|
|
Results: []LintIssue{},
|
|
})
|
|
}
|
|
|
|
// HTTP client for image checks with 5s timeout
|
|
httpClient := &http.Client{Timeout: 5 * time.Second}
|
|
|
|
var allIssues []LintIssue
|
|
|
|
for _, p := range pages {
|
|
// Skip system pages (e.g. _Sidebar, _Footer, _Header)
|
|
if strings.HasPrefix(p.title, "_") {
|
|
continue
|
|
}
|
|
content, err := fetchPageContent(ctx, projectID, p.subURL)
|
|
if err != nil {
|
|
allIssues = append(allIssues, LintIssue{Page: p.title, Level: "error", Check: "fetch", Message: fmt.Sprintf("failed to fetch: %v", err)})
|
|
continue
|
|
}
|
|
|
|
if isCheckEnabled(checkFilter, "empty") {
|
|
allIssues = append(allIssues, checkEmpty(p.title, content)...)
|
|
}
|
|
if isCheckEnabled(checkFilter, "headings") {
|
|
allIssues = append(allIssues, checkHeading(p.title, content)...)
|
|
}
|
|
if isCheckEnabled(checkFilter, "short") {
|
|
allIssues = append(allIssues, checkShort(p.title, content)...)
|
|
}
|
|
if isCheckEnabled(checkFilter, "links") {
|
|
allIssues = append(allIssues, checkDeadLinks(p.title, content, knownTitles)...)
|
|
}
|
|
if isCheckEnabled(checkFilter, "images") {
|
|
allIssues = append(allIssues, checkImages(p.title, content, httpClient)...)
|
|
}
|
|
}
|
|
|
|
// Count errors/warnings
|
|
var errCount, warnCount int
|
|
for _, issue := range allIssues {
|
|
if issue.Level == "error" {
|
|
errCount++
|
|
} else {
|
|
warnCount++
|
|
}
|
|
}
|
|
|
|
// Print issues to stderr
|
|
for _, issue := range allIssues {
|
|
if issue.Level == "error" {
|
|
fmt.Fprintf(os.Stderr, " ✗ %s - %s\n", issue.Page, issue.Message)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, " ⚠ %s - %s\n", issue.Page, issue.Message)
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\nSummary: %d pages, %d errors, %d warnings\n", len(pages), errCount, warnCount)
|
|
|
|
return ctx.OutputData(LintSummary{
|
|
Repository: ctx.Owner + "/" + ctx.Repo,
|
|
TotalPages: len(pages),
|
|
TotalIssues: len(allIssues),
|
|
Errors: errCount,
|
|
Warnings: warnCount,
|
|
Results: allIssues,
|
|
})
|
|
}
|
|
|
|
func Shortcuts() []*common.Shortcut {
|
|
return []*common.Shortcut{
|
|
{
|
|
Name: "list",
|
|
Description: "List all wiki pages",
|
|
Run: func(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q := url.Values{}
|
|
q.Set("owner", ctx.Owner)
|
|
q.Set("repo", ctx.Repo)
|
|
q.Set("projectId", projectID)
|
|
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("wikiPages"), q)
|
|
if err != nil {
|
|
return fmt.Errorf("获取 Wiki 页面列表失败: %w", err)
|
|
}
|
|
cleanWikiList(env)
|
|
return ctx.Output(env)
|
|
},
|
|
},
|
|
{
|
|
Name: "view",
|
|
Description: "View a wiki page",
|
|
Flags: []common.Flag{
|
|
{Name: "title", Short: "t", Usage: "Page title", Required: true},
|
|
},
|
|
Run: func(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q := url.Values{}
|
|
q.Set("owner", ctx.Owner)
|
|
q.Set("repo", ctx.Repo)
|
|
q.Set("projectId", projectID)
|
|
q.Set("pageName", title)
|
|
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("getWiki"), q)
|
|
if err != nil {
|
|
return fmt.Errorf("查看 Wiki 页面失败: %w", err)
|
|
}
|
|
return outputWithDecodedContent(ctx, env)
|
|
},
|
|
},
|
|
{
|
|
Name: "create",
|
|
Description: "Create a wiki page",
|
|
DryRun: true,
|
|
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
|
|
title := ctx.Arg("title")
|
|
return fmt.Sprintf("Create wiki page: %s", title), nil
|
|
},
|
|
Flags: []common.Flag{
|
|
{Name: "title", Short: "t", Usage: "Page title", Required: true},
|
|
{Name: "content", Short: "c", Usage: "Wiki page content (plain text, will be base64-encoded)"},
|
|
{Name: "file", Short: "f", Usage: "Read content from file"},
|
|
{Name: "message", Short: "m", Usage: "Commit message"},
|
|
},
|
|
Run: func(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
content, err := resolveContent(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"owner": ctx.Owner,
|
|
"repo": ctx.Repo,
|
|
"projectId": parseProjectIDInt(projectID),
|
|
"pageName": title,
|
|
"title": title,
|
|
"content_base64": base64.StdEncoding.EncodeToString([]byte(content)),
|
|
}
|
|
if msg := ctx.Arg("message"); msg != "" {
|
|
body["message"] = msg
|
|
}
|
|
|
|
env, err := callWikiAPI(ctx, "POST", wikiPath("createWiki"), body)
|
|
if err != nil {
|
|
return fmt.Errorf("创建 Wiki 页面失败: %w", err)
|
|
}
|
|
return ctx.Output(env)
|
|
},
|
|
},
|
|
{
|
|
Name: "update",
|
|
Description: "Update a wiki page",
|
|
DryRun: true,
|
|
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
|
|
title := ctx.Arg("title")
|
|
return fmt.Sprintf("Update wiki page: %s", title), nil
|
|
},
|
|
Flags: []common.Flag{
|
|
{Name: "page", Short: "p", Usage: "Current page title to find (defaults to --title)"},
|
|
{Name: "title", Short: "t", Usage: "New page title", Required: true},
|
|
{Name: "cover", Short: "c", Usage: "Replace entire page content with this text"},
|
|
{Name: "add", Short: "a", Usage: "Append text to existing page content"},
|
|
{Name: "file", Short: "f", Usage: "Read content from file (used with --cover or --add)"},
|
|
{Name: "message", Short: "m", Usage: "Commit message"},
|
|
},
|
|
Run: func(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pageName := ctx.Arg("page")
|
|
if pageName == "" {
|
|
pageName = title
|
|
}
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
coverText := ctx.Arg("cover")
|
|
addText := ctx.Arg("add")
|
|
filePath := ctx.Arg("file")
|
|
message := ctx.Arg("message")
|
|
|
|
var finalContent string
|
|
if coverText != "" || filePath != "" && coverText == "" && addText == "" {
|
|
// --cover or --file alone: overwrite
|
|
content, err := resolveUpdateContent(ctx, coverText, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
finalContent = content
|
|
} else if addText != "" {
|
|
// --add: append to existing content
|
|
newPart, err := resolveUpdateContent(ctx, addText, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
existing, err := fetchPageContent(ctx, projectID, pageName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch existing page content for append: %w", err)
|
|
}
|
|
finalContent = existing + newPart
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"owner": ctx.Owner,
|
|
"repo": ctx.Repo,
|
|
"projectId": parseProjectIDInt(projectID),
|
|
"pageName": url.QueryEscape(pageName),
|
|
"title": title,
|
|
"message": message,
|
|
}
|
|
if finalContent != "" {
|
|
body["content_base64"] = base64.StdEncoding.EncodeToString([]byte(finalContent))
|
|
}
|
|
|
|
env, err := callWikiAPI(ctx, "PUT", wikiPath("updateWiki"), body)
|
|
if err != nil {
|
|
return fmt.Errorf("更新 Wiki 页面失败: %w", err)
|
|
}
|
|
return ctx.Output(env)
|
|
},
|
|
},
|
|
{
|
|
Name: "delete",
|
|
Description: "Delete a wiki page",
|
|
DryRun: true,
|
|
DryRunHint: func(ctx *common.RuntimeContext) (string, error) {
|
|
title := ctx.Arg("title")
|
|
return fmt.Sprintf("Delete wiki page: %s", title), nil
|
|
},
|
|
Flags: []common.Flag{
|
|
{Name: "title", Short: "t", Usage: "Page title", Required: true},
|
|
},
|
|
Run: func(ctx *common.RuntimeContext) error {
|
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
|
return err
|
|
}
|
|
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
projectID, err := resolveProjectID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"owner": ctx.Owner,
|
|
"repo": ctx.Repo,
|
|
"projectId": parseProjectIDInt(projectID),
|
|
"pageName": title,
|
|
"message": "",
|
|
}
|
|
|
|
_, delErr := callWikiAPI(ctx, "DELETE", wikiPath("deleteWiki"), body)
|
|
if delErr != nil {
|
|
q := url.Values{}
|
|
q.Set("owner", ctx.Owner)
|
|
q.Set("repo", ctx.Repo)
|
|
q.Set("projectId", projectID)
|
|
q.Set("pageName", title)
|
|
_, viewErr := callWikiAPIWithQuery(ctx, "GET", wikiPath("getWiki"), q)
|
|
if viewErr != nil {
|
|
return ctx.OutputData(map[string]string{
|
|
"message": "Wiki page deleted successfully",
|
|
})
|
|
}
|
|
return fmt.Errorf("删除 Wiki 页面失败: %w", delErr)
|
|
}
|
|
return ctx.OutputData(map[string]string{
|
|
"message": "Wiki page deleted successfully",
|
|
})
|
|
},
|
|
},
|
|
{
|
|
Name: "lint",
|
|
Description: "Check wiki pages for quality issues",
|
|
Flags: []common.Flag{
|
|
{Name: "check", Usage: "Specific checks to run (comma-separated): links,headings,images,empty. Default: all"},
|
|
},
|
|
Run: runLint,
|
|
},
|
|
}
|
|
}
|