forked from Gitlink/gitlink-cli
feat(wiki): add OpenAPI shortcuts
This commit is contained in:
parent
52b7093846
commit
053875b071
19
README.md
19
README.md
|
|
@ -96,6 +96,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
||||||
| Category | Capabilities |
|
| Category | Capabilities |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| 📦 Repo | List, create, fork, delete repositories, view repo info, insights, and interactions |
|
| 📦 Repo | List, create, fork, delete repositories, view repo info, insights, and interactions |
|
||||||
|
| Wiki | Create, update, view, list, and delete wiki pages |
|
||||||
| 🐛 Issue | Create, update, close, batch close, comment on issues |
|
| 🐛 Issue | Create, update, close, batch close, comment on issues |
|
||||||
| 🔖 Label | Create, list, update, delete issue labels |
|
| 🔖 Label | Create, list, update, delete issue labels |
|
||||||
| 🔀 PR | Create, merge, review pull requests, view changed files |
|
| 🔀 PR | Create, merge, review pull requests, view changed files |
|
||||||
|
|
@ -246,6 +247,23 @@ gitlink-cli repo +create -n my-project -d "Project description"
|
||||||
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Wiki Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List and view wiki pages
|
||||||
|
gitlink-cli wiki +pages --owner Gitlink --repo forgeplus --project-id 123
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 123 --page Home
|
||||||
|
|
||||||
|
# Create or update wiki pages
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 --page Home --title Home --content "Welcome to Wiki"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 --page Home --title Home --content "Updated content"
|
||||||
|
|
||||||
|
# Preview wiki page deletion
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 123 --page Home --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
### Webhook Management
|
### Webhook Management
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -638,6 +656,7 @@ See [skills/README.md](skills/README.md) for details.
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
|
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
|
||||||
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
|
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
|
||||||
|
| `gitlink-wiki` | Wiki operations (create, update, view, list, delete) |
|
||||||
| `gitlink-issue` | Issue operations (create, update, close, comment, etc.) |
|
| `gitlink-issue` | Issue operations (create, update, close, comment, etc.) |
|
||||||
| `gitlink-pr` | Pull request operations (create, merge, review, etc.) |
|
| `gitlink-pr` | Pull request operations (create, merge, review, etc.) |
|
||||||
| `gitlink-member` | Repository member and invite link management |
|
| `gitlink-member` | Repository member and invite link management |
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@
|
||||||
| 分类 | 能力 |
|
| 分类 | 能力 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
|
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
|
||||||
|
| Wiki | 创建、更新、查看、列出和删除 Wiki 页面 |
|
||||||
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
|
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
|
||||||
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
||||||
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
||||||
|
|
@ -257,6 +258,23 @@ gitlink-cli repo +create -n my-project -d "项目描述"
|
||||||
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Wiki 管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出和查看 Wiki 页面
|
||||||
|
gitlink-cli wiki +pages --owner Gitlink --repo forgeplus --project-id 123
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 123 --page Home
|
||||||
|
|
||||||
|
# 创建或更新 Wiki 页面
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 --page Home --title Home --content "Welcome to Wiki"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 --page Home --title Home --content "Updated content"
|
||||||
|
|
||||||
|
# 预览删除 Wiki 页面
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 123 --page Home --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
### Webhook 管理
|
### Webhook 管理
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -517,6 +535,7 @@ git push gitlink
|
||||||
|-------|------|
|
|-------|------|
|
||||||
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
|
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
|
||||||
| `gitlink-repo` | 仓库操作(创建、查看、删除、Fork、洞察数据等) |
|
| `gitlink-repo` | 仓库操作(创建、查看、删除、Fork、洞察数据等) |
|
||||||
|
| `gitlink-wiki` | Wiki 操作(创建、更新、查看、列表、删除) |
|
||||||
| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
|
| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
|
||||||
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
|
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
|
||||||
| `gitlink-member` | 仓库成员与邀请链接管理 |
|
| `gitlink-member` | 仓库成员与邀请链接管理 |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Wiki OpenAPI Shortcuts
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This change adds a dedicated `wiki` shortcut group for GitLink Wiki OpenAPI
|
||||||
|
coverage. It includes read, create, update, and delete workflows.
|
||||||
|
|
||||||
|
## Added shortcuts
|
||||||
|
|
||||||
|
| Shortcut | API |
|
||||||
|
|----------|-----|
|
||||||
|
| `wiki +pages` | `GET /api/wiki/wikiPages` |
|
||||||
|
| `wiki +view` | `GET /api/wiki/getWiki` |
|
||||||
|
| `wiki +create` | `POST /api/wiki/createWiki` |
|
||||||
|
| `wiki +update` | `PUT /api/wiki/updateWiki` |
|
||||||
|
| `wiki +delete` | `DELETE /api/wiki/deleteWiki` |
|
||||||
|
|
||||||
|
`wiki +create` and `wiki +update` accept either raw `--content`, which the CLI
|
||||||
|
base64-encodes, or pre-encoded `--content-base64`. `wiki +delete` supports
|
||||||
|
`--dry-run` for safe previews.
|
||||||
|
|
||||||
|
## Submitter
|
||||||
|
|
||||||
|
Wang Yue
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/gitlink-org/gitlink-cli/shortcuts/search"
|
"github.com/gitlink-org/gitlink-cli/shortcuts/search"
|
||||||
"github.com/gitlink-org/gitlink-cli/shortcuts/user"
|
"github.com/gitlink-org/gitlink-cli/shortcuts/user"
|
||||||
"github.com/gitlink-org/gitlink-cli/shortcuts/webhook"
|
"github.com/gitlink-org/gitlink-cli/shortcuts/webhook"
|
||||||
|
"github.com/gitlink-org/gitlink-cli/shortcuts/wiki"
|
||||||
"github.com/gitlink-org/gitlink-cli/shortcuts/workflow"
|
"github.com/gitlink-org/gitlink-cli/shortcuts/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
||||||
"compare": compare.Shortcuts(),
|
"compare": compare.Shortcuts(),
|
||||||
"webhook": webhook.Shortcuts(tr),
|
"webhook": webhook.Shortcuts(tr),
|
||||||
"health": health.Shortcuts(tr),
|
"health": health.Shortcuts(tr),
|
||||||
|
"wiki": wiki.Shortcuts(),
|
||||||
"workflow": workflow.Shortcuts(),
|
"workflow": workflow.Shortcuts(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +72,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
||||||
"compare": "Compare branches, tags, or commits",
|
"compare": "Compare branches, tags, or commits",
|
||||||
"webhook": tr.T("cmd.webhook.short"),
|
"webhook": tr.T("cmd.webhook.short"),
|
||||||
"health": "Project health data collection",
|
"health": "Project health data collection",
|
||||||
|
"wiki": "Wiki operations",
|
||||||
"workflow": "AI agent workflow analysis",
|
"workflow": "AI agent workflow analysis",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ func TestRegisterAll(t *testing.T) {
|
||||||
"repo", "issue", "label", "license", "pr", "release", "branch",
|
"repo", "issue", "label", "license", "pr", "release", "branch",
|
||||||
"org", "user", "search", "ci", "workflow",
|
"org", "user", "search", "ci", "workflow",
|
||||||
"compare", "member", "milestone", "pipeline", "webhook",
|
"compare", "member", "milestone", "pipeline", "webhook",
|
||||||
"health",
|
"health", "wiki",
|
||||||
}
|
}
|
||||||
|
|
||||||
groupSet := map[string]bool{}
|
groupSet := map[string]bool{}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
package wiki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Shortcuts() []*common.Shortcut {
|
||||||
|
return []*common.Shortcut{
|
||||||
|
{
|
||||||
|
Name: "pages",
|
||||||
|
Description: "List repository wiki pages",
|
||||||
|
Flags: wikiProjectFlags(),
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q, err := wikiProjectQuery(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPIWithQuery("GET", "/wiki/wikiPages", q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "view",
|
||||||
|
Description: "Show a wiki page",
|
||||||
|
Flags: append(wikiProjectFlags(),
|
||||||
|
common.Flag{Name: "page", Short: "p", Usage: "Wiki page name", Required: true},
|
||||||
|
),
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q, err := wikiProjectQuery(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
page, err := ctx.RequireArg("page")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q.Set("pageName", page)
|
||||||
|
env, err := ctx.CallAPIWithQuery("GET", "/wiki/getWiki", q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "create",
|
||||||
|
Description: "Create a wiki page",
|
||||||
|
Flags: wikiWriteFlags(true),
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload, err := wikiWritePayload(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("POST", "/wiki/createWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "update",
|
||||||
|
Description: "Update a wiki page",
|
||||||
|
Flags: wikiWriteFlags(false),
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload, err := wikiWritePayload(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("PUT", "/wiki/updateWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "delete",
|
||||||
|
Description: "Delete a wiki page",
|
||||||
|
Flags: append(wikiProjectFlags(),
|
||||||
|
common.Flag{Name: "page", Short: "p", Usage: "Wiki page name", Required: true},
|
||||||
|
common.Flag{Name: "dry-run", Usage: "Preview the delete request without changing wiki state", Bool: true, Default: "false"},
|
||||||
|
),
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload, err := wikiBasePayload(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
page, err := ctx.RequireArg("page")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload["pageName"] = page
|
||||||
|
if ctx.Arg("dry-run") == "true" {
|
||||||
|
return ctx.OutputData(map[string]interface{}{
|
||||||
|
"dry_run": true,
|
||||||
|
"action": "delete_wiki_page",
|
||||||
|
"method": "DELETE",
|
||||||
|
"path": "/wiki/deleteWiki",
|
||||||
|
"payload": payload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("DELETE", "/wiki/deleteWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiProjectFlags() []common.Flag {
|
||||||
|
return []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiWriteFlags(contentRequired bool) []common.Flag {
|
||||||
|
return append(wikiProjectFlags(),
|
||||||
|
common.Flag{Name: "page", Short: "p", Usage: "Wiki page name", Required: true},
|
||||||
|
common.Flag{Name: "title", Short: "t", Usage: "Wiki page title", Required: true},
|
||||||
|
common.Flag{Name: "message", Short: "m", Usage: "Commit message"},
|
||||||
|
common.Flag{Name: "content", Usage: "Wiki content; encoded to base64 before sending"},
|
||||||
|
common.Flag{Name: "content-base64", Usage: "Pre-encoded wiki content"},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiProjectQuery(ctx *common.RuntimeContext) (url.Values, error) {
|
||||||
|
projectID, err := wikiProjectID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
q := url.Values{}
|
||||||
|
q.Set("owner", ctx.Owner)
|
||||||
|
q.Set("repo", ctx.Repo)
|
||||||
|
q.Set("projectId", strconv.Itoa(projectID))
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiWritePayload(ctx *common.RuntimeContext, contentRequired bool) (map[string]interface{}, error) {
|
||||||
|
payload, err := wikiBasePayload(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
page, err := ctx.RequireArg("page")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
title, err := ctx.RequireArg("title")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
content, ok, err := wikiContent(ctx, contentRequired)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payload["pageName"] = page
|
||||||
|
payload["title"] = title
|
||||||
|
if message := ctx.Arg("message"); message != "" {
|
||||||
|
payload["message"] = message
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
payload["content_base64"] = content
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiBasePayload(ctx *common.RuntimeContext) (map[string]interface{}, error) {
|
||||||
|
projectID, err := wikiProjectID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"owner": ctx.Owner,
|
||||||
|
"repo": ctx.Repo,
|
||||||
|
"projectId": projectID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiProjectID(ctx *common.RuntimeContext) (int, error) {
|
||||||
|
value, err := ctx.RequireArg("project-id")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(value)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return 0, fmt.Errorf("--project-id must be a positive integer")
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wikiContent(ctx *common.RuntimeContext, required bool) (string, bool, error) {
|
||||||
|
content := ctx.Arg("content")
|
||||||
|
encoded := ctx.Arg("content-base64")
|
||||||
|
if content != "" && encoded != "" {
|
||||||
|
return "", false, fmt.Errorf("--content cannot be used with --content-base64")
|
||||||
|
}
|
||||||
|
if content != "" {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(content)), true, nil
|
||||||
|
}
|
||||||
|
if encoded != "" {
|
||||||
|
return encoded, true, nil
|
||||||
|
}
|
||||||
|
if required {
|
||||||
|
return "", false, fmt.Errorf("required flag --content is missing (or use --content-base64)")
|
||||||
|
}
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
package wiki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||||
|
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||||
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWikiShortcutsRouteToOpenAPIEndpoints(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args map[string]string
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
query map[string]string
|
||||||
|
payload map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pages",
|
||||||
|
args: map[string]string{"project-id": "123"},
|
||||||
|
method: "GET",
|
||||||
|
path: "/wiki/wikiPages.json",
|
||||||
|
query: map[string]string{"owner": "owner", "repo": "repo", "projectId": "123"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "view",
|
||||||
|
args: map[string]string{"project-id": "123", "page": "Home"},
|
||||||
|
method: "GET",
|
||||||
|
path: "/wiki/getWiki.json",
|
||||||
|
query: map[string]string{"owner": "owner", "repo": "repo", "projectId": "123", "pageName": "Home"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create",
|
||||||
|
args: map[string]string{"project-id": "123", "page": "Home", "title": "Home", "message": "Add Home", "content": "hello"},
|
||||||
|
method: "POST",
|
||||||
|
path: "/wiki/createWiki.json",
|
||||||
|
payload: map[string]interface{}{
|
||||||
|
"owner": "owner",
|
||||||
|
"repo": "repo",
|
||||||
|
"projectId": float64(123),
|
||||||
|
"pageName": "Home",
|
||||||
|
"title": "Home",
|
||||||
|
"message": "Add Home",
|
||||||
|
"content_base64": "aGVsbG8=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update",
|
||||||
|
args: map[string]string{"project-id": "123", "page": "Home", "title": "Home", "content-base64": "dXBkYXRlZA=="},
|
||||||
|
method: "PUT",
|
||||||
|
path: "/wiki/updateWiki.json",
|
||||||
|
payload: map[string]interface{}{
|
||||||
|
"owner": "owner",
|
||||||
|
"repo": "repo",
|
||||||
|
"projectId": float64(123),
|
||||||
|
"pageName": "Home",
|
||||||
|
"title": "Home",
|
||||||
|
"content_base64": "dXBkYXRlZA==",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete",
|
||||||
|
args: map[string]string{"project-id": "123", "page": "Home"},
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/wiki/deleteWiki.json",
|
||||||
|
payload: map[string]interface{}{
|
||||||
|
"owner": "owner",
|
||||||
|
"repo": "repo",
|
||||||
|
"projectId": float64(123),
|
||||||
|
"pageName": "Home",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
server := newWikiTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != tc.method || r.URL.Path != tc.path {
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
|
assertWikiQuery(t, r, tc.query)
|
||||||
|
if tc.payload != nil {
|
||||||
|
assertWikiPayload(t, r, tc.payload)
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
writeWikiJSON(t, w, map[string]interface{}{"message": "success", "data": map[string]interface{}{}})
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(server, tc.name, tc.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s shortcut failed: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
if !called {
|
||||||
|
t.Fatal("expected API request")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiDeleteDryRunDoesNotCallAPI(t *testing.T) {
|
||||||
|
server := newWikiTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(server, "delete", map[string]string{
|
||||||
|
"project-id": "123",
|
||||||
|
"page": "Home",
|
||||||
|
"dry-run": "true",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("delete dry-run failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiShortcutsValidateRequiredArgs(t *testing.T) {
|
||||||
|
server := newWikiTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args map[string]string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "pages", args: map[string]string{}, want: "--project-id"},
|
||||||
|
{name: "pages", args: map[string]string{"project-id": "abc"}, want: "--project-id must be a positive integer"},
|
||||||
|
{name: "view", args: map[string]string{"project-id": "123"}, want: "--page"},
|
||||||
|
{name: "create", args: map[string]string{"project-id": "123", "page": "Home", "title": "Home"}, want: "--content"},
|
||||||
|
{name: "create", args: map[string]string{"project-id": "123", "page": "Home", "title": "Home", "content": "x", "content-base64": "eA=="}, want: "--content cannot be used with --content-base64"},
|
||||||
|
{name: "update", args: map[string]string{"project-id": "123", "page": "Home"}, want: "--title"},
|
||||||
|
{name: "delete", args: map[string]string{"project-id": "123"}, want: "--page"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := runWikiShortcut(server, tc.name, tc.args)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected validation error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.want) {
|
||||||
|
t.Fatalf("error = %q, want it to mention %s", err.Error(), tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWikiShortcut(server *httptest.Server, name string, args map[string]string) error {
|
||||||
|
for _, shortcut := range Shortcuts() {
|
||||||
|
if shortcut.Name != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx := &common.RuntimeContext{
|
||||||
|
Client: &client.Client{
|
||||||
|
HTTP: server.Client(),
|
||||||
|
BaseURL: server.URL,
|
||||||
|
},
|
||||||
|
Owner: "owner",
|
||||||
|
Repo: "repo",
|
||||||
|
Format: "json",
|
||||||
|
Args: args,
|
||||||
|
Tr: i18n.Default(),
|
||||||
|
}
|
||||||
|
return shortcut.Run(ctx)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("shortcut %q not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWikiTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
return httptest.NewServer(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertWikiQuery(t *testing.T, r *http.Request, want map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
query := r.URL.Query()
|
||||||
|
if len(query) != len(want) {
|
||||||
|
t.Fatalf("query = %v, want %v", query, want)
|
||||||
|
}
|
||||||
|
for key, value := range want {
|
||||||
|
if got := query.Get(key); got != value {
|
||||||
|
t.Fatalf("query %s = %q, want %q", key, got, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertWikiPayload(t *testing.T, r *http.Request, want map[string]interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
var got map[string]interface{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
|
||||||
|
t.Fatalf("decode request body: %v", err)
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("payload = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
for key, value := range want {
|
||||||
|
if got[key] != value {
|
||||||
|
t.Fatalf("payload %s = %v, want %v", key, got[key], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeWikiJSON(t *testing.T, w http.ResponseWriter, payload interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||||
|
t.Fatalf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
name: gitlink-wiki
|
||||||
|
version: 1.0.0
|
||||||
|
description: "Wiki management: list, view, create, update, and delete GitLink wiki pages."
|
||||||
|
metadata:
|
||||||
|
requires:
|
||||||
|
bins: ["gitlink-cli"]
|
||||||
|
cliHelp: "gitlink-cli wiki --help"
|
||||||
|
---
|
||||||
|
|
||||||
|
# gitlink-wiki
|
||||||
|
|
||||||
|
Read `../gitlink-shared/SKILL.md` first for authentication, global flags, and safety rules.
|
||||||
|
|
||||||
|
Write and delete operations must be confirmed by the user before execution. Use
|
||||||
|
`wiki +delete --dry-run` before deleting a page.
|
||||||
|
|
||||||
|
## Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Purpose | Auth |
|
||||||
|
|----------|---------|------|
|
||||||
|
| `wiki +pages` | List wiki pages | No for public repos |
|
||||||
|
| `wiki +view` | Show one wiki page | No for public repos |
|
||||||
|
| `wiki +create` | Create a wiki page | Yes |
|
||||||
|
| `wiki +update` | Update a wiki page | Yes |
|
||||||
|
| `wiki +delete` | Delete a wiki page | Yes |
|
||||||
|
|
||||||
|
All shortcuts require `--project-id` because the Wiki OpenAPI requires the
|
||||||
|
GitLink project ID in addition to owner and repo.
|
||||||
|
|
||||||
|
`wiki +create` and `wiki +update` accept either `--content`, which the CLI
|
||||||
|
base64-encodes, or `--content-base64` for callers that already have encoded
|
||||||
|
content.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gitlink-cli wiki +pages --owner Gitlink --repo forgeplus --project-id 123
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 123 --page Home
|
||||||
|
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 \
|
||||||
|
--page Home \
|
||||||
|
--title Home \
|
||||||
|
--content "Welcome to Wiki"
|
||||||
|
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus \
|
||||||
|
--project-id 123 \
|
||||||
|
--page Home \
|
||||||
|
--title Home \
|
||||||
|
--content "Updated content"
|
||||||
|
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 123 --page Home --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Mapping
|
||||||
|
|
||||||
|
| Shortcut | API |
|
||||||
|
|----------|-----|
|
||||||
|
| `wiki +pages` | `GET /api/wiki/wikiPages` |
|
||||||
|
| `wiki +view` | `GET /api/wiki/getWiki` |
|
||||||
|
| `wiki +create` | `POST /api/wiki/createWiki` |
|
||||||
|
| `wiki +update` | `PUT /api/wiki/updateWiki` |
|
||||||
|
| `wiki +delete` | `DELETE /api/wiki/deleteWiki` |
|
||||||
Loading…
Reference in New Issue