feat(shortcut): add shortcuts/wiki
This commit is contained in:
parent
982f2cb336
commit
2c9a8b6192
23
README.md
23
README.md
|
|
@ -114,6 +114,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
||||||
| 🔧 CI | View builds, logs, CI/CD operations |
|
| 🔧 CI | View builds, logs, CI/CD operations |
|
||||||
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
|
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
|
||||||
| 🔔 Webhook | Manage repo webhooks and test deliveries |
|
| 🔔 Webhook | Manage repo webhooks and test deliveries |
|
||||||
|
| 📖 Wiki | List, view, create, update, and delete wiki pages |
|
||||||
| 🔍 Search | Search repositories, users |
|
| 🔍 Search | Search repositories, users |
|
||||||
| 📊 Dataset | Query research datasets by project |
|
| 📊 Dataset | Query research datasets by project |
|
||||||
| 👤 User | View user profiles and info |
|
| 👤 User | View user profiles and info |
|
||||||
|
|
@ -273,6 +274,28 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
|
||||||
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Wiki Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List wiki pages (table of contents)
|
||||||
|
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
|
||||||
|
|
||||||
|
# View a wiki page by page name
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
|
||||||
|
|
||||||
|
# Create a wiki page
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||||
|
-n getting-started -t "Getting Started" -c "# Getting Started Guide"
|
||||||
|
|
||||||
|
# Update a wiki page title and/or content
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# Updated content"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title" -c "New content"
|
||||||
|
|
||||||
|
# Delete a wiki page
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
|
||||||
|
```
|
||||||
|
|
||||||
### Member Management
|
### Member Management
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@
|
||||||
| 🏢 组织 | 管理组织、成员、团队 |
|
| 🏢 组织 | 管理组织、成员、团队 |
|
||||||
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
|
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
|
||||||
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
|
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
|
||||||
|
| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
|
||||||
| 🔍 搜索 | 搜索仓库、用户 |
|
| 🔍 搜索 | 搜索仓库、用户 |
|
||||||
| 📊 数据集 | 按项目查询科研数据集 |
|
| 📊 数据集 | 按项目查询科研数据集 |
|
||||||
| 👤 用户 | 查看用户资料和信息 |
|
| 👤 用户 | 查看用户资料和信息 |
|
||||||
|
|
@ -284,6 +285,28 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
|
||||||
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Wiki 管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出 Wiki 页面(目录结构)
|
||||||
|
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
|
||||||
|
|
||||||
|
# 查看 Wiki 页面
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
|
||||||
|
|
||||||
|
# 创建 Wiki 页面
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||||
|
-n getting-started -t "快速开始" -c "# 快速开始指南"
|
||||||
|
|
||||||
|
# 更新 Wiki 页面标题和/或内容
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# 更新后的内容"
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题" -c "新内容"
|
||||||
|
|
||||||
|
# 删除 Wiki 页面
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
|
||||||
|
```
|
||||||
|
|
||||||
### 成员管理
|
### 成员管理
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Wiki Shortcut
|
||||||
|
|
||||||
|
新增 `wiki` Shortcut 组,支持 Wiki 页面管理:
|
||||||
|
|
||||||
|
- `wiki +list` - 列出 Wiki 页面(目录结构)
|
||||||
|
- `wiki +view` - 按页面名称查看 Wiki 页面详情
|
||||||
|
- `wiki +create` - 创建新的 Wiki 页面
|
||||||
|
- `wiki +update` - 更新 Wiki 页面标题和/或内容
|
||||||
|
- `wiki +delete` - 删除 Wiki 页面
|
||||||
|
|
||||||
|
## 实现要点
|
||||||
|
|
||||||
|
- **API 端点**:基于 `/api/wiki/open/{action}` 扁平路径结构,覆盖 5 个 Wiki 管理接口:
|
||||||
|
- `GET /api/wiki/open/wikiPages` — 目录列表
|
||||||
|
- `GET /api/wiki/open/getWiki` — 查看页面
|
||||||
|
- `POST /api/wiki/open/createWiki` — 创建页面
|
||||||
|
- `PUT /api/wiki/open/updateWiki` — 更新页面
|
||||||
|
- `DELETE /api/wiki/open/deleteWiki` — 删除页面
|
||||||
|
- **标识方式**:Wiki 页面通过 `pageName`(slug)标识,所有操作需要 `projectId`(GitLink 项目数字 ID)
|
||||||
|
- **内容编码**:创建和更新时,内容自动进行 base64 编码后以 `content_base64` 字段发送
|
||||||
|
- **更新保护**:`+update` 要求必须提供 `--title` 和 `--page-name`;`--content` 为可选
|
||||||
|
- **Shortcut 模式**:使用 `common.Shortcut` + `RuntimeContext` 框架,与其他模块保持一致
|
||||||
|
|
||||||
|
|
@ -115,22 +115,35 @@ func (c *Client) Do(method, path string, body interface{}, query url.Values) (*o
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check GitLink error-in-body pattern
|
// Check GitLink error-in-body pattern
|
||||||
|
// Support both {"status":N, "message":"..."} and gateway {"code":N, "msg":"..."}
|
||||||
|
var bodyCode float64
|
||||||
|
var bodyMsg string
|
||||||
if status, ok := raw["status"]; ok {
|
if status, ok := raw["status"]; ok {
|
||||||
var statusCode float64
|
|
||||||
switch v := status.(type) {
|
switch v := status.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
statusCode = v
|
bodyCode = v
|
||||||
case int:
|
case int:
|
||||||
statusCode = float64(v)
|
bodyCode = float64(v)
|
||||||
}
|
}
|
||||||
if statusCode != 0 && statusCode != 200 && statusCode != 1 {
|
bodyMsg, _ = raw["message"].(string)
|
||||||
msg, _ := raw["message"].(string)
|
} else if code, ok := raw["code"]; ok {
|
||||||
suggestion := suggestFix(int(statusCode))
|
switch v := code.(type) {
|
||||||
return output.ErrorEnvelope(int(statusCode), msg, suggestion), &APIError{
|
case float64:
|
||||||
StatusCode: int(statusCode),
|
bodyCode = v
|
||||||
Code: int(statusCode),
|
case int:
|
||||||
Message: msg,
|
bodyCode = float64(v)
|
||||||
}
|
}
|
||||||
|
bodyMsg, _ = raw["msg"].(string)
|
||||||
|
if bodyMsg == "" {
|
||||||
|
bodyMsg, _ = raw["message"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bodyCode != 0 && bodyCode != 200 && bodyCode != 201 && bodyCode != 204 && bodyCode != 1 {
|
||||||
|
suggestion := suggestFix(int(bodyCode))
|
||||||
|
return output.ErrorEnvelope(int(bodyCode), bodyMsg, suggestion), &APIError{
|
||||||
|
StatusCode: int(bodyCode),
|
||||||
|
Code: int(bodyCode),
|
||||||
|
Message: bodyMsg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,6 +183,10 @@ func shouldAppendJSONSuffix(path string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Wiki open API endpoints do not use .json suffix
|
||||||
|
if len(parts) >= 3 && parts[0] == "wiki" && parts[1] == "open" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,45 @@ func TestClientDoStatusError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientDoGatewayCodeError(t *testing.T) {
|
||||||
|
// Gateway returns {"code":N, "msg":"..."} instead of {"status":N, "message":"..."}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"code":400,"msg":"Bad Request"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||||
|
env, err := c.Do("GET", "/api/test", nil, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for code=400")
|
||||||
|
}
|
||||||
|
if env == nil {
|
||||||
|
t.Fatal("expected envelope for code error")
|
||||||
|
}
|
||||||
|
if env.OK {
|
||||||
|
t.Fatal("expected OK=false for code=400")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDoGatewayCode201Success(t *testing.T) {
|
||||||
|
// Gateway returns code=201 with JSON string data — should be treated as success
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"code":201,"msg":"","data":"{\"title\":\"test\"}"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||||
|
env, err := c.Do("POST", "/api/test", map[string]string{"title": "test"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error for code=201: %v", err)
|
||||||
|
}
|
||||||
|
if !env.OK {
|
||||||
|
t.Fatal("expected OK=true for code=201")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientDoStatusZero(t *testing.T) {
|
func TestClientDoStatusZero(t *testing.T) {
|
||||||
// status=0, 200, 1 are treated as success
|
// status=0, 200, 1 are treated as success
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -525,3 +564,18 @@ func TestShouldAppendJSONSuffixSkipsExistingJSONPath(t *testing.T) {
|
||||||
t.Fatal("existing .json path should not get another suffix")
|
t.Fatal("existing .json path should not get another suffix")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldAppendJSONSuffixSkipsWikiOpenPaths(t *testing.T) {
|
||||||
|
paths := []string{
|
||||||
|
"/wiki/open/createWiki",
|
||||||
|
"/wiki/open/getWiki",
|
||||||
|
"/wiki/open/updateWiki",
|
||||||
|
"/wiki/open/deleteWiki",
|
||||||
|
"/wiki/open/wikiPages",
|
||||||
|
}
|
||||||
|
for _, p := range paths {
|
||||||
|
if shouldAppendJSONSuffix(p) {
|
||||||
|
t.Errorf("wiki/open path %q should not get .json suffix", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultBaseURL = "https://www.gitlink.org.cn/api"
|
DefaultBaseURL = "https://www.gitlink.org.cn/api"
|
||||||
|
DefaultGatewayURL = "https://gateway.gitlink.org.cn/api"
|
||||||
DefaultFormat = "table"
|
DefaultFormat = "table"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
BaseURL string `yaml:"base_url"`
|
BaseURL string `yaml:"base_url"`
|
||||||
|
GatewayURL string `yaml:"gateway_url"`
|
||||||
Format string `yaml:"default_format"`
|
Format string `yaml:"default_format"`
|
||||||
Editor string `yaml:"editor,omitempty"`
|
Editor string `yaml:"editor,omitempty"`
|
||||||
Pager string `yaml:"pager,omitempty"`
|
Pager string `yaml:"pager,omitempty"`
|
||||||
|
|
@ -23,6 +25,7 @@ type Config struct {
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
BaseURL: DefaultBaseURL,
|
BaseURL: DefaultBaseURL,
|
||||||
|
GatewayURL: DefaultGatewayURL,
|
||||||
Format: DefaultFormat,
|
Format: DefaultFormat,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +57,9 @@ func Load() (*Config, error) {
|
||||||
if cfg.BaseURL == "" {
|
if cfg.BaseURL == "" {
|
||||||
cfg.BaseURL = DefaultBaseURL
|
cfg.BaseURL = DefaultBaseURL
|
||||||
}
|
}
|
||||||
|
if cfg.GatewayURL == "" {
|
||||||
|
cfg.GatewayURL = DefaultGatewayURL
|
||||||
|
}
|
||||||
if cfg.Format == "" {
|
if cfg.Format == "" {
|
||||||
cfg.Format = DefaultFormat
|
cfg.Format = DefaultFormat
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +86,8 @@ func Get(key string) (string, error) {
|
||||||
switch key {
|
switch key {
|
||||||
case "base_url":
|
case "base_url":
|
||||||
return cfg.BaseURL, nil
|
return cfg.BaseURL, nil
|
||||||
|
case "gateway_url":
|
||||||
|
return cfg.GatewayURL, nil
|
||||||
case "default_format":
|
case "default_format":
|
||||||
return cfg.Format, nil
|
return cfg.Format, nil
|
||||||
case "editor":
|
case "editor":
|
||||||
|
|
@ -101,6 +109,8 @@ func Set(key, value string) error {
|
||||||
switch key {
|
switch key {
|
||||||
case "base_url":
|
case "base_url":
|
||||||
cfg.BaseURL = value
|
cfg.BaseURL = value
|
||||||
|
case "gateway_url":
|
||||||
|
cfg.GatewayURL = value
|
||||||
case "default_format":
|
case "default_format":
|
||||||
cfg.Format = value
|
cfg.Format = value
|
||||||
case "editor":
|
case "editor":
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -53,6 +54,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
||||||
"compare": compare.Shortcuts(),
|
"compare": compare.Shortcuts(),
|
||||||
"dataset": dataset.Shortcuts(tr),
|
"dataset": dataset.Shortcuts(tr),
|
||||||
"webhook": webhook.Shortcuts(tr),
|
"webhook": webhook.Shortcuts(tr),
|
||||||
|
"wiki": wiki.Shortcuts(),
|
||||||
"health": health.Shortcuts(tr),
|
"health": health.Shortcuts(tr),
|
||||||
"ignore": ignore.Shortcuts(),
|
"ignore": ignore.Shortcuts(),
|
||||||
"workflow": workflow.Shortcuts(),
|
"workflow": workflow.Shortcuts(),
|
||||||
|
|
@ -77,6 +79,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
||||||
"compare": "Compare branches, tags, or commits",
|
"compare": "Compare branches, tags, or commits",
|
||||||
"dataset": tr.T("cmd.dataset.short"),
|
"dataset": tr.T("cmd.dataset.short"),
|
||||||
"webhook": tr.T("cmd.webhook.short"),
|
"webhook": tr.T("cmd.webhook.short"),
|
||||||
|
"wiki": "Wiki page management",
|
||||||
"health": "Project health data collection",
|
"health": "Project health data collection",
|
||||||
"ignore": tr.T("cmd.ignore.short"),
|
"ignore": tr.T("cmd.ignore.short"),
|
||||||
"workflow": "AI agent workflow analysis",
|
"workflow": "AI agent workflow analysis",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ func TestRegisterAll(t *testing.T) {
|
||||||
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
|
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
|
||||||
"org", "user", "search", "ci", "workflow",
|
"org", "user", "search", "ci", "workflow",
|
||||||
"compare", "member", "milestone", "pipeline", "webhook",
|
"compare", "member", "milestone", "pipeline", "webhook",
|
||||||
"dataset", "health", "ignore",
|
"dataset", "health", "ignore", "wiki",
|
||||||
}
|
}
|
||||||
|
|
||||||
groupSet := map[string]bool{}
|
groupSet := map[string]bool{}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
package wiki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gitlink-org/gitlink-cli/internal/config"
|
||||||
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// switchToGateway overrides the client base URL with the gateway URL from config.
|
||||||
|
func switchToGateway(ctx *common.RuntimeContext) error {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cfg.GatewayURL == "" {
|
||||||
|
cfg.GatewayURL = config.DefaultGatewayURL
|
||||||
|
}
|
||||||
|
ctx.Client.BaseURL = cfg.GatewayURL
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gatewayFlag returns the common --gateway flag definition.
|
||||||
|
func gatewayFlag() common.Flag {
|
||||||
|
return common.Flag{Name: "gateway", Short: "g", Usage: "Use gateway API endpoint", Bool: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcuts returns all wiki shortcuts.
|
||||||
|
func Shortcuts() []*common.Shortcut {
|
||||||
|
return []*common.Shortcut{
|
||||||
|
{
|
||||||
|
Name: "list",
|
||||||
|
Description: "List wiki pages",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
gatewayFlag(),
|
||||||
|
},
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if ctx.Arg("gateway") == "true" {
|
||||||
|
if err := switchToGateway(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q := url.Values{}
|
||||||
|
q.Set("owner", ctx.Owner)
|
||||||
|
q.Set("repo", ctx.Repo)
|
||||||
|
q.Set("projectId", ctx.Arg("project-id"))
|
||||||
|
env, err := ctx.CallAPIWithQuery("GET", "/wiki/open/wikiPages", q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "view",
|
||||||
|
Description: "View a wiki page by page name",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
{Name: "page-name", Short: "n", Usage: "Wiki page name (slug)", Required: true},
|
||||||
|
gatewayFlag(),
|
||||||
|
},
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if ctx.Arg("gateway") == "true" {
|
||||||
|
if err := switchToGateway(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q := url.Values{}
|
||||||
|
q.Set("owner", ctx.Owner)
|
||||||
|
q.Set("repo", ctx.Repo)
|
||||||
|
q.Set("projectId", ctx.Arg("project-id"))
|
||||||
|
q.Set("pageName", ctx.Arg("page-name"))
|
||||||
|
env, err := ctx.CallAPIWithQuery("GET", "/wiki/open/getWiki", q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "create",
|
||||||
|
Description: "Create a new wiki page",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
{Name: "page-name", Short: "n", Usage: "Wiki page name (slug)", Required: true},
|
||||||
|
{Name: "title", Short: "t", Usage: "Wiki page title", Required: true},
|
||||||
|
{Name: "content", Short: "c", Usage: "Wiki page content (markdown)", Required: true},
|
||||||
|
{Name: "message", Short: "m", Usage: "Commit message"},
|
||||||
|
gatewayFlag(),
|
||||||
|
},
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if ctx.Arg("gateway") == "true" {
|
||||||
|
if err := switchToGateway(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
content := ctx.Arg("content")
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"owner": ctx.Owner,
|
||||||
|
"repo": ctx.Repo,
|
||||||
|
"projectId": ctx.Arg("project-id"),
|
||||||
|
"pageName": ctx.Arg("page-name"),
|
||||||
|
"title": ctx.Arg("title"),
|
||||||
|
"content_base64": base64.StdEncoding.EncodeToString([]byte(content)),
|
||||||
|
"message": ctx.Arg("message"),
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("POST", "/wiki/open/createWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "update",
|
||||||
|
Description: "Update an existing wiki page",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
{Name: "page-name", Short: "n", Usage: "Wiki page name (slug)", Required: true},
|
||||||
|
{Name: "title", Short: "t", Usage: "Wiki page title", Required: true},
|
||||||
|
{Name: "content", Short: "c", Usage: "Wiki page content (markdown)"},
|
||||||
|
{Name: "message", Short: "m", Usage: "Commit message"},
|
||||||
|
gatewayFlag(),
|
||||||
|
},
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if ctx.Arg("gateway") == "true" {
|
||||||
|
if err := switchToGateway(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
title := ctx.Arg("title")
|
||||||
|
if title == "" {
|
||||||
|
return fmt.Errorf("--title is required")
|
||||||
|
}
|
||||||
|
content := ctx.Arg("content")
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"owner": ctx.Owner,
|
||||||
|
"repo": ctx.Repo,
|
||||||
|
"projectId": ctx.Arg("project-id"),
|
||||||
|
"pageName": ctx.Arg("page-name"),
|
||||||
|
"title": title,
|
||||||
|
"message": ctx.Arg("message"),
|
||||||
|
}
|
||||||
|
if content != "" {
|
||||||
|
payload["content_base64"] = base64.StdEncoding.EncodeToString([]byte(content))
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("PUT", "/wiki/open/updateWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "delete",
|
||||||
|
Description: "Delete a wiki page",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "project-id", Usage: "GitLink project ID", Required: true},
|
||||||
|
{Name: "page-name", Short: "n", Usage: "Wiki page name (slug)", Required: true},
|
||||||
|
gatewayFlag(),
|
||||||
|
},
|
||||||
|
Run: func(ctx *common.RuntimeContext) error {
|
||||||
|
if ctx.Arg("gateway") == "true" {
|
||||||
|
if err := switchToGateway(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"owner": ctx.Owner,
|
||||||
|
"repo": ctx.Repo,
|
||||||
|
"projectId": ctx.Arg("project-id"),
|
||||||
|
"pageName": ctx.Arg("page-name"),
|
||||||
|
}
|
||||||
|
env, err := ctx.CallAPI("DELETE", "/wiki/open/deleteWiki", payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.Output(env)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
package wiki
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||||
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWikiList(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "GET", "/wiki/open/wikiPages")
|
||||||
|
assertEqual(t, r.URL.Query().Get("owner"), "owner")
|
||||||
|
assertEqual(t, r.URL.Query().Get("repo"), "repo")
|
||||||
|
assertEqual(t, r.URL.Query().Get("projectId"), "12345")
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "data": []interface{}{}})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "list", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiView(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "GET", "/wiki/open/getWiki")
|
||||||
|
assertEqual(t, r.URL.Query().Get("owner"), "owner")
|
||||||
|
assertEqual(t, r.URL.Query().Get("repo"), "repo")
|
||||||
|
assertEqual(t, r.URL.Query().Get("projectId"), "12345")
|
||||||
|
assertEqual(t, r.URL.Query().Get("pageName"), "home")
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "data": map[string]interface{}{"title": "home"}})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "view", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "home",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("view shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiCreate(t *testing.T) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "POST", "/wiki/open/createWiki")
|
||||||
|
payload = decodeJSON(t, r)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "create", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "new-page",
|
||||||
|
"title": "New Page",
|
||||||
|
"content": "# Hello",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, payload["owner"], "owner")
|
||||||
|
assertEqual(t, payload["repo"], "repo")
|
||||||
|
assertEqual(t, payload["pageName"], "new-page")
|
||||||
|
assertEqual(t, payload["title"], "New Page")
|
||||||
|
if _, ok := payload["content_base64"]; !ok {
|
||||||
|
t.Fatal("body missing content_base64")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiUpdate(t *testing.T) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "PUT", "/wiki/open/updateWiki")
|
||||||
|
payload = decodeJSON(t, r)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "update", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "home",
|
||||||
|
"title": "Updated Title",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("update shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, payload["owner"], "owner")
|
||||||
|
assertEqual(t, payload["pageName"], "home")
|
||||||
|
assertEqual(t, payload["title"], "Updated Title")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiUpdateRequiresTitle(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatalf("server should not be called when title is missing: %s %s", r.Method, r.URL.Path)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "update", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "home",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected update without --title to return an error")
|
||||||
|
}
|
||||||
|
if err.Error() != "--title is required" {
|
||||||
|
t.Fatalf("unexpected error message: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiUpdateWithContentOnly(t *testing.T) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "PUT", "/wiki/open/updateWiki")
|
||||||
|
payload = decodeJSON(t, r)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "update", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "home",
|
||||||
|
"title": "Existing Title",
|
||||||
|
"content": "# Updated content",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("update with content failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := payload["content_base64"]; !ok {
|
||||||
|
t.Fatal("body missing content_base64 when --content provided")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWikiDelete(t *testing.T) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
assertRequest(t, r, "DELETE", "/wiki/open/deleteWiki")
|
||||||
|
payload = decodeJSON(t, r)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runWikiShortcut(t, server, "delete", map[string]string{
|
||||||
|
"project-id": "12345",
|
||||||
|
"page-name": "old-page",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("delete shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, payload["owner"], "owner")
|
||||||
|
assertEqual(t, payload["repo"], "repo")
|
||||||
|
assertEqual(t, payload["pageName"], "old-page")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWikiShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||||
|
t.Helper()
|
||||||
|
shortcut := findWikiShortcut(t, name)
|
||||||
|
ctx := &common.RuntimeContext{
|
||||||
|
Client: &client.Client{
|
||||||
|
HTTP: server.Client(),
|
||||||
|
BaseURL: server.URL,
|
||||||
|
},
|
||||||
|
Owner: "owner",
|
||||||
|
Repo: "repo",
|
||||||
|
Format: "json",
|
||||||
|
Args: args,
|
||||||
|
}
|
||||||
|
if ctx.Args == nil {
|
||||||
|
ctx.Args = map[string]string{}
|
||||||
|
}
|
||||||
|
return shortcut.Run(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findWikiShortcut(t *testing.T, name string) *common.Shortcut {
|
||||||
|
t.Helper()
|
||||||
|
for _, shortcut := range Shortcuts() {
|
||||||
|
if shortcut.Name == name {
|
||||||
|
return shortcut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("shortcut %q not found", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertRequest(t *testing.T, r *http.Request, method, path string) {
|
||||||
|
t.Helper()
|
||||||
|
if r.Method != method || r.URL.Path != path {
|
||||||
|
t.Fatalf("got request %s %s, want %s %s", r.Method, r.URL.Path, method, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
||||||
|
t.Helper()
|
||||||
|
var payload map[string]interface{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
t.Fatalf("failed to decode request body: %v", err)
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqual(t *testing.T, got interface{}, want interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -110,6 +110,8 @@ skills/
|
||||||
│ └── ci-workflow.md # CI 工作流
|
│ └── ci-workflow.md # CI 工作流
|
||||||
├── gitlink-pipeline/ # 流水线工作流
|
├── gitlink-pipeline/ # 流水线工作流
|
||||||
│ └── SKILL.md # Pipeline 操作指南
|
│ └── SKILL.md # Pipeline 操作指南
|
||||||
|
├── gitlink-wiki/ # Wiki 页面管理
|
||||||
|
│ └── SKILL.md # Wiki 操作指南
|
||||||
├── gitlink-pm/ # 项目管理
|
├── gitlink-pm/ # 项目管理
|
||||||
│ └── SKILL.md # PM 操作指南
|
│ └── SKILL.md # PM 操作指南
|
||||||
├── gitlink-health/ # 项目健康度分析
|
├── gitlink-health/ # 项目健康度分析
|
||||||
|
|
@ -150,6 +152,7 @@ skills/
|
||||||
| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` |
|
| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` |
|
||||||
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
|
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
|
||||||
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
|
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
|
||||||
|
| **gitlink-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
|
||||||
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
|
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
|
||||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
||||||
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
|
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
name: gitlink-wiki
|
||||||
|
version: 2.0.0
|
||||||
|
description: "Wiki 页面管理:查看目录、查看、创建、更新和删除 GitLink Wiki 页面。"
|
||||||
|
metadata:
|
||||||
|
requires:
|
||||||
|
bins: ["gitlink-cli"]
|
||||||
|
cliHelp: "gitlink-cli wiki --help"
|
||||||
|
---
|
||||||
|
|
||||||
|
# gitlink-wiki
|
||||||
|
|
||||||
|
**重要**: 开始操作前请先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中涵盖认证、权限、全局参数和 GitLink API 行为说明。
|
||||||
|
**重要**: 执行写入或破坏性操作(如 `+create`、`+update` 或 `+delete`)前,请先确认用户意图。
|
||||||
|
**重要**: 操作 GitLink 资源请使用 `gitlink-cli`,不要使用 `gh` 等 GitHub 专用工具。
|
||||||
|
|
||||||
|
## 快捷命令
|
||||||
|
|
||||||
|
| 快捷命令 | 说明 | 操作类型 |
|
||||||
|
|----------|------|----------|
|
||||||
|
| `wiki +list` | 列出 Wiki 页面(目录结构) | 只读 |
|
||||||
|
| `wiki +view` | 按页面名称查看 Wiki 页面详情 | 只读 |
|
||||||
|
| `wiki +create` | 创建新的 Wiki 页面 | 写入 |
|
||||||
|
| `wiki +update` | 更新 Wiki 页面标题和/或内容 | 写入 |
|
||||||
|
| `wiki +delete` | 删除 Wiki 页面 | 破坏性 |
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出 Wiki 目录
|
||||||
|
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
|
||||||
|
|
||||||
|
# 查看 Wiki 页面
|
||||||
|
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
|
||||||
|
|
||||||
|
# 创建 Wiki 页面
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||||
|
-n getting-started -t "快速开始" -c "# 快速开始指南\n\n这是入门文档。"
|
||||||
|
|
||||||
|
# 创建时附带提交信息
|
||||||
|
gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||||
|
-n api-guide -t "API 指南" -c "# API 指南" -m "Add API guide"
|
||||||
|
|
||||||
|
# 仅更新页面标题
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题"
|
||||||
|
|
||||||
|
# 仅更新页面内容
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# 更新后的内容"
|
||||||
|
|
||||||
|
# 同时更新标题和内容
|
||||||
|
gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 \
|
||||||
|
-n home -t "新标题" -c "新内容"
|
||||||
|
|
||||||
|
# 删除 Wiki 页面
|
||||||
|
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参数说明
|
||||||
|
|
||||||
|
| 命令 | 关键参数 |
|
||||||
|
|------|----------|
|
||||||
|
| `+list` | `--project-id` |
|
||||||
|
| `+view` | `--project-id`、`--page-name` (`-n`) |
|
||||||
|
| `+create` | `--project-id`、`--page-name` (`-n`)、`--title` (`-t`)、`--content` (`-c`)、`--message` (`-m`) |
|
||||||
|
| `+update` | `--project-id`、`--page-name` (`-n`)、`--title` (`-t`),可选 `--content` (`-c`)、`--message` (`-m`) |
|
||||||
|
| `+delete` | `--project-id`、`--page-name` (`-n`) |
|
||||||
|
|
||||||
|
## API 说明
|
||||||
|
|
||||||
|
- 所有 Wiki 端点使用 `/api/wiki/{action}` 扁平路径结构(非 REST 嵌套路径)。
|
||||||
|
- 目录列表: `GET /api/wiki/wikiPages`(查询参数:owner, repo, projectId)
|
||||||
|
- 查看详情: `GET /api/wiki/getWiki`(查询参数:owner, repo, projectId, pageName)
|
||||||
|
- 创建页面: `POST /api/wiki/createWiki`(JSON body,content 需 base64 编码)
|
||||||
|
- 更新页面: `PUT /api/wiki/updateWiki`(JSON body,content 需 base64 编码)
|
||||||
|
- 删除页面: `DELETE /api/wiki/deleteWiki`(JSON body:owner, repo, projectId, pageName)
|
||||||
|
- Wiki 页面通过 `pageName`(slug)标识,而非数字 ID。
|
||||||
|
- 所有操作都需要 `--project-id`(GitLink 项目数字 ID)。
|
||||||
|
- 创建和更新时,内容自动进行 base64 编码后以 `content_base64` 字段发送。
|
||||||
|
- `+update` 要求必须提供 `--title` 和 `--page-name`;`--content` 为可选。
|
||||||
Loading…
Reference in New Issue