forked from Gitlink/gitlink-cli
fix:统一错误提示优化+Issue标签ID动态获取
This commit is contained in:
parent
297a2ccbbe
commit
89afa968fd
|
|
@ -67,10 +67,10 @@ func runAPI(c *cobra.Command, args []string) error {
|
|||
env, err := cli.Do(method, path, body, query)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*client.APIError); ok {
|
||||
errEnv := output.ErrorEnvelope(apiErr.Code, apiErr.Message, "")
|
||||
errEnv := output.ErrorEnvelope(apiErr.Code, apiErr.Message, apiErr.Suggestion)
|
||||
return output.Print(errEnv, resolveFormat())
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("API 请求失败 [%s %s]: %w", method, path, err)
|
||||
}
|
||||
|
||||
return output.Print(env, resolveFormat())
|
||||
|
|
|
|||
BIN
gitlink-cli.exe
BIN
gitlink-cli.exe
Binary file not shown.
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/auth"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/config"
|
||||
clierrors "github.com/gitlink-org/gitlink-cli/internal/errors"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
)
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ type APIError struct {
|
|||
StatusCode int
|
||||
Code interface{}
|
||||
Message string
|
||||
Kind clierrors.ErrorKind
|
||||
Suggestion string
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
|
|
@ -101,10 +104,13 @@ func (c *Client) Do(method, path string, body interface{}, query url.Values) (*o
|
|||
|
||||
// Check HTTP-level errors
|
||||
if resp.StatusCode >= 400 {
|
||||
info := lookupStatusInfo(resp.StatusCode)
|
||||
return nil, &APIError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Code: resp.StatusCode,
|
||||
Message: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(respData))),
|
||||
Kind: info.kind,
|
||||
Suggestion: info.suggestion,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,11 +132,13 @@ func (c *Client) Do(method, path string, body interface{}, query url.Values) (*o
|
|||
}
|
||||
if statusCode != 0 && statusCode != 200 && statusCode != 1 {
|
||||
msg, _ := raw["message"].(string)
|
||||
suggestion := suggestFix(int(statusCode))
|
||||
return output.ErrorEnvelope(int(statusCode), msg, suggestion), &APIError{
|
||||
info := lookupStatusInfo(int(statusCode))
|
||||
return output.ErrorEnvelope(int(statusCode), msg, info.suggestion), &APIError{
|
||||
StatusCode: int(statusCode),
|
||||
Code: int(statusCode),
|
||||
Message: msg,
|
||||
Kind: info.kind,
|
||||
Suggestion: info.suggestion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,17 +185,43 @@ func (c *Client) Delete(path string, query url.Values) (*output.Envelope, error)
|
|||
return c.Do("DELETE", path, nil, query)
|
||||
}
|
||||
|
||||
func suggestFix(code int) string {
|
||||
switch code {
|
||||
case 401:
|
||||
return "请先运行 gitlink-cli auth login 登录"
|
||||
case 403:
|
||||
return "权限不足,请确认账户权限或联系项目管理员"
|
||||
case 404:
|
||||
return "资源不存在,请检查 owner/repo/id 是否正确"
|
||||
case 422:
|
||||
return "参数校验失败,请检查请求参数"
|
||||
default:
|
||||
return ""
|
||||
type statusInfo struct {
|
||||
kind clierrors.ErrorKind
|
||||
message string
|
||||
suggestion string
|
||||
}
|
||||
|
||||
var statusMessages = map[int]statusInfo{
|
||||
-2: {clierrors.KindAuth, "未登录或 Token 已过期",
|
||||
"运行 gitlink-cli auth login 重新登录,或检查 GITLINK_TOKEN 环境变量"},
|
||||
-1: {clierrors.KindInput, "参数校验失败",
|
||||
"检查必填参数是否缺失、参数格式是否正确,运行 gitlink-cli <命令> --help 查看用法"},
|
||||
0: {clierrors.KindUnknown, "操作失败", ""},
|
||||
// Standard HTTP codes
|
||||
401: {clierrors.KindAuth, "认证失败",
|
||||
"运行 gitlink-cli auth login 登录,或检查 GITLINK_TOKEN 环境变量"},
|
||||
403: {clierrors.KindForbidden, "权限不足",
|
||||
"请确认账号有此仓库的访问权限,或联系项目管理员"},
|
||||
404: {clierrors.KindNotFound, "资源不存在",
|
||||
"检查 owner/repo/id 是否正确,资源可能已被删除"},
|
||||
422: {clierrors.KindInput, "参数校验失败",
|
||||
"检查请求参数格式,运行 gitlink-cli <命令> --help 查看用法"},
|
||||
429: {clierrors.KindServer, "请求过于频繁",
|
||||
"稍等片刻后重试"},
|
||||
500: {clierrors.KindServer, "服务器内部错误",
|
||||
"稍等后重试,如持续出现请联系平台管理员"},
|
||||
502: {clierrors.KindServer, "网关错误",
|
||||
"服务器暂时不可用,稍等后重试"},
|
||||
503: {clierrors.KindServer, "服务暂时不可用",
|
||||
"服务器正在维护,稍等后重试"},
|
||||
}
|
||||
|
||||
func lookupStatusInfo(code int) statusInfo {
|
||||
if info, ok := statusMessages[code]; ok {
|
||||
return info
|
||||
}
|
||||
return statusInfo{
|
||||
kind: clierrors.KindUnknown,
|
||||
message: fmt.Sprintf("API 返回错误码 %d", code),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func ResolveOwnerRepo(flagOwner, flagRepo string) (string, string, error) {
|
|||
owner, repo, err := fromGitRemote()
|
||||
if err != nil {
|
||||
if flagOwner == "" || flagRepo == "" {
|
||||
return "", "", fmt.Errorf("cannot detect owner/repo from git remote: %w\nUse --owner and --repo flags to specify explicitly", err)
|
||||
return "", "", fmt.Errorf("无法自动检测 owner/repo: %w\n 请使用 --owner 和 --repo 参数显式指定,或切换到 git 仓库目录下执行", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorKind categorizes errors by user-actionability.
|
||||
type ErrorKind string
|
||||
|
||||
const (
|
||||
KindAuth ErrorKind = "auth" // Login/token issues — user can re-login
|
||||
KindInput ErrorKind = "input" // Parameter issues — user can fix arguments
|
||||
KindConfig ErrorKind = "config" // Config file issues — user can edit config
|
||||
KindNetwork ErrorKind = "network" // Network issues — user can check/retry
|
||||
KindGit ErrorKind = "git" // Git repo issues — user needs correct directory
|
||||
KindServer ErrorKind = "server" // Server-side error — user should wait or contact admin
|
||||
KindNotFound ErrorKind = "not_found" // Resource not found — user can check ID
|
||||
KindForbidden ErrorKind = "forbidden" // Permission denied — user can request access
|
||||
KindUnknown ErrorKind = "unknown" // Unclassified error
|
||||
)
|
||||
|
||||
// CLIError is the unified CLI error type with multi-layered information.
|
||||
type CLIError struct {
|
||||
Kind ErrorKind // Error category for programmatic handling
|
||||
Message string // Human-readable description of what went wrong
|
||||
Detail string // Low-level technical detail (shown in debug mode)
|
||||
Suggestion string // Actionable advice for the user
|
||||
Command string // The command that triggered the error (e.g., "issue +create")
|
||||
Cause error // The underlying error
|
||||
}
|
||||
|
||||
func (e *CLIError) Error() string {
|
||||
var b strings.Builder
|
||||
|
||||
// Header line: kind + command
|
||||
b.WriteString(string(e.Kind))
|
||||
b.WriteString(" error")
|
||||
if e.Command != "" {
|
||||
b.WriteString(" — ")
|
||||
b.WriteString(e.Command)
|
||||
}
|
||||
|
||||
// Body: message
|
||||
if e.Message != "" {
|
||||
b.WriteString("\n\n reason: ")
|
||||
b.WriteString(e.Message)
|
||||
}
|
||||
|
||||
// Suggestion
|
||||
if e.Suggestion != "" {
|
||||
b.WriteString("\n suggestion: ")
|
||||
b.WriteString(e.Suggestion)
|
||||
}
|
||||
|
||||
// Detail (always included in Error() so users see the raw cause)
|
||||
if e.Detail != "" {
|
||||
b.WriteString("\n detail: ")
|
||||
b.WriteString(e.Detail)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (e *CLIError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
// New creates a CLIError with the given parameters.
|
||||
func New(kind ErrorKind, message, suggestion string) *CLIError {
|
||||
return &CLIError{
|
||||
Kind: kind,
|
||||
Message: message,
|
||||
Suggestion: suggestion,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap creates a CLIError that wraps an underlying cause.
|
||||
func Wrap(kind ErrorKind, message, suggestion string, cause error) *CLIError {
|
||||
return &CLIError{
|
||||
Kind: kind,
|
||||
Message: message,
|
||||
Suggestion: suggestion,
|
||||
Cause: cause,
|
||||
Detail: cause.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithCommand sets the command context on the error.
|
||||
func (e *CLIError) WithCommand(cmd string) *CLIError {
|
||||
e.Command = cmd
|
||||
return e
|
||||
}
|
||||
|
||||
// InputError is a convenience constructor for parameter errors.
|
||||
func InputError(message, suggestion string) *CLIError {
|
||||
return New(KindInput, message, suggestion)
|
||||
}
|
||||
|
||||
// AuthError is a convenience constructor for authentication errors.
|
||||
func AuthError(message, suggestion string) *CLIError {
|
||||
return New(KindAuth, message, suggestion)
|
||||
}
|
||||
|
||||
// ConfigError creates a config-related error with the config file path in the suggestion.
|
||||
func ConfigError(message string, cause error) *CLIError {
|
||||
return Wrap(KindConfig, message,
|
||||
fmt.Sprintf("检查配置文件 %s 是否正确", configPathPlaceholder()), cause)
|
||||
}
|
||||
|
||||
// configPathPlaceholder avoids circular import; the actual path will be resolved
|
||||
// in output formatting.
|
||||
func configPathPlaceholder() string {
|
||||
return "~/.config/gitlink-cli/config.yaml"
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/v1"+ctx.RepoPath()+"/branches", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取分支列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -41,7 +41,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := ctx.RequireArg("name")
|
||||
name, err := ctx.RequireArg("name", "--name feature/new-thing")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
from := ctx.Arg("from")
|
||||
if from == "" {
|
||||
from = "master"
|
||||
|
|
@ -52,7 +55,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", "/v1"+ctx.RepoPath()+"/branches", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建分支失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -67,13 +70,16 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := ctx.RequireArg("name")
|
||||
name, err := ctx.RequireArg("name", "--name feature/new-thing")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"branch_name": name,
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", "/v1"+ctx.RepoPath()+"/branches/delete", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("删除分支失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -88,13 +94,16 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := ctx.RequireArg("name")
|
||||
name, err := ctx.RequireArg("name", "--name feature/new-thing")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"branch_name": name,
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/protected_branches", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("设置分支保护失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -109,11 +118,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := ctx.RequireArg("name")
|
||||
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/protected_branches/%s", ctx.RepoPath(), url.PathEscape(name)), nil)
|
||||
name, err := ctx.RequireArg("name", "--name feature/new-thing")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/protected_branches/%s", ctx.RepoPath(), url.PathEscape(name)), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("取消分支保护失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/builds", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 CI 构建列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -42,7 +42,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
build, _ := ctx.RequireArg("build")
|
||||
build, err := ctx.RequireArg("build", "--build 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stage := ctx.Arg("stage")
|
||||
step := ctx.Arg("step")
|
||||
if stage == "" {
|
||||
|
|
@ -53,7 +56,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/builds/%s/logs/%s/%s", ctx.RepoPath(), build, stage, step), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取构建日志失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -68,11 +71,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
build, _ := ctx.RequireArg("build")
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/builds/%s/restart", ctx.RepoPath(), build), nil)
|
||||
build, err := ctx.RequireArg("build", "--build 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/builds/%s/restart", ctx.RepoPath(), build), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重启构建失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -86,11 +92,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
build, _ := ctx.RequireArg("build")
|
||||
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/builds/%s/stop", ctx.RepoPath(), build), nil)
|
||||
build, err := ctx.RequireArg("build", "--build 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/builds/%s/stop", ctx.RepoPath(), build), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("停止构建失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ func MountShortcut(parent *cobra.Command, s *Shortcut) {
|
|||
}
|
||||
}
|
||||
|
||||
ctx, err := NewRuntimeContext(flagValues)
|
||||
commandName := parent.Use + " +" + s.Name
|
||||
ctx, err := NewRuntimeContext(flagValues, commandName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -36,20 +37,21 @@ func MountShortcut(parent *cobra.Command, s *Shortcut) {
|
|||
}
|
||||
|
||||
for _, f := range s.Flags {
|
||||
usage := f.Usage
|
||||
if f.Required {
|
||||
usage = usage + " [required]"
|
||||
}
|
||||
if f.Bool {
|
||||
defaultValue, _ := strconv.ParseBool(f.Default)
|
||||
if f.Short != "" {
|
||||
cmd.Flags().BoolP(f.Name, f.Short, defaultValue, f.Usage)
|
||||
cmd.Flags().BoolP(f.Name, f.Short, defaultValue, usage)
|
||||
} else {
|
||||
cmd.Flags().Bool(f.Name, defaultValue, f.Usage)
|
||||
cmd.Flags().Bool(f.Name, defaultValue, usage)
|
||||
}
|
||||
} else if f.Short != "" {
|
||||
cmd.Flags().StringP(f.Name, f.Short, f.Default, f.Usage)
|
||||
cmd.Flags().StringP(f.Name, f.Short, f.Default, usage)
|
||||
} else {
|
||||
cmd.Flags().String(f.Name, f.Default, f.Usage)
|
||||
}
|
||||
if f.Required {
|
||||
cmd.MarkFlagRequired(f.Name)
|
||||
cmd.Flags().String(f.Name, f.Default, usage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/context"
|
||||
clierrors "github.com/gitlink-org/gitlink-cli/internal/errors"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
)
|
||||
|
||||
|
|
@ -31,15 +32,16 @@ type Flag struct {
|
|||
|
||||
// RuntimeContext provides helpers for shortcut implementations.
|
||||
type RuntimeContext struct {
|
||||
Client *client.Client
|
||||
Owner string
|
||||
Repo string
|
||||
Format string
|
||||
Args map[string]string
|
||||
Client *client.Client
|
||||
Owner string
|
||||
Repo string
|
||||
Format string
|
||||
CommandName string
|
||||
Args map[string]string
|
||||
}
|
||||
|
||||
// NewRuntimeContext creates a RuntimeContext with auto-resolved owner/repo.
|
||||
func NewRuntimeContext(args map[string]string) (*RuntimeContext, error) {
|
||||
func NewRuntimeContext(args map[string]string, commandName string) (*RuntimeContext, error) {
|
||||
cli, err := client.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -52,11 +54,12 @@ func NewRuntimeContext(args map[string]string) (*RuntimeContext, error) {
|
|||
}
|
||||
|
||||
return &RuntimeContext{
|
||||
Client: cli,
|
||||
Owner: cmdutil.Owner,
|
||||
Repo: cmdutil.Repo,
|
||||
Format: format,
|
||||
Args: args,
|
||||
Client: cli,
|
||||
Owner: cmdutil.Owner,
|
||||
Repo: cmdutil.Repo,
|
||||
Format: format,
|
||||
CommandName: commandName,
|
||||
Args: args,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -109,11 +112,18 @@ func (ctx *RuntimeContext) Arg(name string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// RequireArg returns a flag value or an error if not set.
|
||||
func (ctx *RuntimeContext) RequireArg(name string) (string, error) {
|
||||
// RequireArg returns a flag value or a CLIError if not set.
|
||||
func (ctx *RuntimeContext) RequireArg(name, example string) (string, error) {
|
||||
v := ctx.Arg(name)
|
||||
if v == "" {
|
||||
return "", fmt.Errorf("required flag --%s is missing", name)
|
||||
suggestion := fmt.Sprintf("请提供 --%s 参数", name)
|
||||
if example != "" {
|
||||
suggestion += fmt.Sprintf(",例如:%s", example)
|
||||
}
|
||||
return "", clierrors.InputError(
|
||||
fmt.Sprintf("required flag --%s is missing", name),
|
||||
suggestion,
|
||||
).WithCommand(ctx.CommandName)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,15 @@ var tagIDs = map[string]int{
|
|||
"搁置": 315532,
|
||||
}
|
||||
|
||||
// labelNames returns all known tag names from the given mapping.
|
||||
func labelNames(tags map[string]int) string {
|
||||
var names []string
|
||||
for name := range tags {
|
||||
names = append(names, name)
|
||||
}
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
// BatchResult is a single item result in a batch operation.
|
||||
type BatchResult struct {
|
||||
Number string `json:"number" yaml:"number"`
|
||||
|
|
@ -566,15 +575,16 @@ func parseTracker(label string) (int, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// parseLabel converts a label name to its GitLink tag ID
|
||||
func parseLabel(name string) (int, error) {
|
||||
if id, ok := tagIDs[name]; ok && id != 0 {
|
||||
// parseLabel converts a label name to its GitLink tag ID.
|
||||
// tags is the project's name→id mapping from resolveIssueTags.
|
||||
func parseLabel(name string, tags map[string]int) (int, error) {
|
||||
if id, ok := tags[name]; ok && id != 0 {
|
||||
return id, nil
|
||||
}
|
||||
if id, err := strconv.Atoi(name); err == nil {
|
||||
return id, nil
|
||||
}
|
||||
return 0, fmt.Errorf("invalid label %q: not found in tagIDs mapping", name)
|
||||
return 0, fmt.Errorf("invalid label %q: not found in project issue tags", name)
|
||||
}
|
||||
|
||||
func collectIssueNumbers(numbersValue, csvPath string) ([]string, error) {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,76 @@ package issue
|
|||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
var issueTagCache sync.Map
|
||||
|
||||
// resolveIssueTags fetches the project's issue tags and returns a name→id mapping.
|
||||
// Results are cached per owner/repo.
|
||||
func resolveIssueTags(ctx *common.RuntimeContext) (map[string]int, error) {
|
||||
key := ctx.Owner + "/" + ctx.Repo
|
||||
if cached, ok := issueTagCache.Load(key); ok {
|
||||
return cached.(map[string]int), nil
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/v1/%s/%s/issue_tags", ctx.Owner, ctx.Repo)
|
||||
q := url.Values{}
|
||||
q.Set("only_name", "true")
|
||||
env, err := ctx.CallAPIWithQuery("GET", path, q)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取项目标签列表失败: %w", err)
|
||||
}
|
||||
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("标签列表响应格式异常")
|
||||
}
|
||||
|
||||
rawTags, ok := data["issue_tags"].([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("标签列表响应缺少 issue_tags 字段")
|
||||
}
|
||||
|
||||
tags := make(map[string]int, len(rawTags))
|
||||
for _, item := range rawTags {
|
||||
tag, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, _ := tag["name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
var id int
|
||||
switch v := tag["id"].(type) {
|
||||
case float64:
|
||||
id = int(v)
|
||||
case int:
|
||||
id = v
|
||||
default:
|
||||
id, _ = strconv.Atoi(fmt.Sprintf("%v", v))
|
||||
}
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
tags[name] = id
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
return nil, fmt.Errorf("项目没有配置任何标签,请先在 GitLink 网页端创建标签")
|
||||
}
|
||||
|
||||
issueTagCache.Store(key, tags)
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func newBatchCreateShortcut() *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "batch-create",
|
||||
|
|
@ -49,6 +113,11 @@ func runBatchCreate(ctx *common.RuntimeContext) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tags, err := resolveIssueTags(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dryRun := parseBool(ctx.Arg("dry-run"))
|
||||
template := strings.ToLower(strings.TrimSpace(ctx.Arg("template")))
|
||||
|
||||
|
|
@ -99,7 +168,7 @@ func runBatchCreate(ctx *common.RuntimeContext) error {
|
|||
continue
|
||||
}
|
||||
|
||||
body := buildCreateBody(ctx, input, template)
|
||||
body := buildCreateBody(ctx, input, template, tags)
|
||||
env, err := ctx.CallAPI("POST", v1RepoPath(ctx)+"/issues", body)
|
||||
if err != nil {
|
||||
result.Status = "failed"
|
||||
|
|
@ -126,7 +195,7 @@ func runBatchCreate(ctx *common.RuntimeContext) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func buildCreateBody(ctx *common.RuntimeContext, input createIssueInput, template string) map[string]interface{} {
|
||||
func buildCreateBody(ctx *common.RuntimeContext, input createIssueInput, template string, tags map[string]int) map[string]interface{} {
|
||||
statusID := statusNew
|
||||
if input.Status != "" {
|
||||
if sid, err := parseStatus(input.Status); err == nil {
|
||||
|
|
@ -143,9 +212,9 @@ func buildCreateBody(ctx *common.RuntimeContext, input createIssueInput, templat
|
|||
if template != "" {
|
||||
body["description"] = buildTemplateDescription(input, template)
|
||||
if template == "bug" {
|
||||
body["issue_tag_ids"] = []interface{}{tagIDs["缺陷"]}
|
||||
body["issue_tag_ids"] = []interface{}{tags["缺陷"]}
|
||||
} else if template == "feature" {
|
||||
body["issue_tag_ids"] = []interface{}{tagIDs["功能"]}
|
||||
body["issue_tag_ids"] = []interface{}{tags["功能"]}
|
||||
}
|
||||
} else if input.Body != "" {
|
||||
body["description"] = input.Body
|
||||
|
|
@ -157,7 +226,7 @@ func buildCreateBody(ctx *common.RuntimeContext, input createIssueInput, templat
|
|||
}
|
||||
}
|
||||
if input.Label != "" {
|
||||
if tid, err := parseLabel(input.Label); err == nil {
|
||||
if tid, err := parseLabel(input.Label, tags); err == nil {
|
||||
body["issue_tag_ids"] = []interface{}{tid}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// testTags is a static name→id mapping used by unit tests.
|
||||
var testTags = map[string]int{
|
||||
"缺陷": 315526,
|
||||
"功能": 315527,
|
||||
"文档": 315533,
|
||||
"任务": 315530,
|
||||
"测试": 315534,
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
|
||||
func findShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
|
|
@ -25,6 +34,21 @@ func findShortcut(t *testing.T, name string) *common.Shortcut {
|
|||
return nil
|
||||
}
|
||||
|
||||
// mockTagsHandler returns a handler that responds to the issue_tags API.
|
||||
func mockTagsHandler(t *testing.T) http.HandlerFunc {
|
||||
t.Helper()
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tags := make([]map[string]interface{}, 0, len(testTags))
|
||||
for name, id := range testTags {
|
||||
tags = append(tags, map[string]interface{}{
|
||||
"id": float64(id),
|
||||
"name": name,
|
||||
})
|
||||
}
|
||||
writeJSONResp(t, w, map[string]interface{}{"issue_tags": tags})
|
||||
}
|
||||
}
|
||||
|
||||
func runBatchCreateShortcut(t *testing.T, server *httptest.Server, args map[string]string) error {
|
||||
t.Helper()
|
||||
s := findShortcut(t, "batch-create")
|
||||
|
|
@ -57,6 +81,10 @@ func decodeReqBody(t *testing.T, r *http.Request) map[string]interface{} {
|
|||
|
||||
func TestBatchCreate_DryRun(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags") {
|
||||
mockTagsHandler(t)(w, r)
|
||||
return
|
||||
}
|
||||
t.Fatalf("no API calls expected in dry-run mode, got %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
|
@ -75,6 +103,8 @@ func TestBatchCreate_FromTitles(t *testing.T) {
|
|||
callCount := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags"):
|
||||
mockTagsHandler(t)(w, r)
|
||||
case r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/v1/owner/repo/issues"):
|
||||
callCount++
|
||||
body := decodeReqBody(t, r)
|
||||
|
|
@ -114,6 +144,10 @@ func TestBatchCreate_FromTitles(t *testing.T) {
|
|||
|
||||
func TestBatchCreate_NoTitles(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags") {
|
||||
mockTagsHandler(t)(w, r)
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
|
@ -131,12 +165,15 @@ func TestBatchCreate_FromCSV(t *testing.T) {
|
|||
|
||||
var created []map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/v1/owner/repo/issues") {
|
||||
switch {
|
||||
case r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags"):
|
||||
mockTagsHandler(t)(w, r)
|
||||
case r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/v1/owner/repo/issues"):
|
||||
created = append(created, decodeReqBody(t, r))
|
||||
writeJSONResp(t, w, map[string]interface{}{"project_issues_index": float64(1)})
|
||||
return
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -161,6 +198,10 @@ func TestBatchCreate_CSVMissingTitleColumn(t *testing.T) {
|
|||
csvPath := writeTempCSV(t, "name,description\nval1,desc1\n")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags") {
|
||||
mockTagsHandler(t)(w, r)
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
|
@ -175,6 +216,10 @@ func TestBatchCreate_CSVOnlyHeader(t *testing.T) {
|
|||
csvPath := writeTempCSV(t, "title,description\n")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags") {
|
||||
mockTagsHandler(t)(w, r)
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
|
@ -188,12 +233,19 @@ func TestBatchCreate_CSVOnlyHeader(t *testing.T) {
|
|||
func TestBatchCreate_PartialFailure(t *testing.T) {
|
||||
callCount := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
if callCount == 2 {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
return
|
||||
switch {
|
||||
case r.Method == "GET" && strings.Contains(r.URL.Path, "/issue_tags"):
|
||||
mockTagsHandler(t)(w, r)
|
||||
case r.Method == "POST" && strings.HasPrefix(r.URL.Path, "/v1/owner/repo/issues"):
|
||||
callCount++
|
||||
if callCount == 2 {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
writeJSONResp(t, w, map[string]interface{}{"project_issues_index": float64(callCount)})
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
writeJSONResp(t, w, map[string]interface{}{"project_issues_index": float64(callCount)})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -223,7 +275,7 @@ func intVal(v interface{}) int {
|
|||
func TestBuildCreateBody_Basic(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{Owner: "o", Repo: "r", Args: map[string]string{}}
|
||||
input := createIssueInput{Title: "Test issue", Status: "new"}
|
||||
body := buildCreateBody(ctx, input, "")
|
||||
body := buildCreateBody(ctx, input, "", testTags)
|
||||
if body["subject"] != "Test issue" {
|
||||
t.Fatalf("subject: got %v", body["subject"])
|
||||
}
|
||||
|
|
@ -248,7 +300,7 @@ func TestBuildCreateBody_BugTemplate(t *testing.T) {
|
|||
Expected: "正常登录",
|
||||
Actual: "报错 500",
|
||||
}
|
||||
body := buildCreateBody(ctx, input, "bug")
|
||||
body := buildCreateBody(ctx, input, "bug", testTags)
|
||||
if body["subject"] != "登录报错" {
|
||||
t.Fatalf("subject: got %v", body["subject"])
|
||||
}
|
||||
|
|
@ -266,8 +318,8 @@ func TestBuildCreateBody_BugTemplate(t *testing.T) {
|
|||
t.Fatal("bug template missing issue_tag_ids")
|
||||
} else {
|
||||
ids := rawTags.([]interface{})
|
||||
if intVal(ids[0]) != tagIDs["缺陷"] {
|
||||
t.Fatalf("tag: got %v (type %T), want %v", ids[0], ids[0], tagIDs["缺陷"])
|
||||
if intVal(ids[0]) != testTags["缺陷"] {
|
||||
t.Fatalf("tag: got %v (type %T), want %v", ids[0], ids[0], testTags["缺陷"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -279,7 +331,7 @@ func TestBuildCreateBody_FeatureTemplate(t *testing.T) {
|
|||
UserStory: "作为用户,我想搜索内容",
|
||||
Acceptance: "搜索结果正确显示",
|
||||
}
|
||||
body := buildCreateBody(ctx, input, "feature")
|
||||
body := buildCreateBody(ctx, input, "feature", testTags)
|
||||
desc, _ := body["description"].(string)
|
||||
if !strings.Contains(desc, "## 用户故事") {
|
||||
t.Fatal("feature description missing user story header")
|
||||
|
|
@ -299,14 +351,14 @@ func TestBuildCreateBody_WithPriorityLabel(t *testing.T) {
|
|||
Priority: "high",
|
||||
Label: "缺陷",
|
||||
}
|
||||
body := buildCreateBody(ctx, input, "")
|
||||
body := buildCreateBody(ctx, input, "", testTags)
|
||||
if intVal(body["priority_id"]) != 3 {
|
||||
t.Fatalf("priority_id: got %v (type %T), want 3 (high)", body["priority_id"], body["priority_id"])
|
||||
}
|
||||
if rawTags, ok := body["issue_tag_ids"]; ok {
|
||||
ids := rawTags.([]interface{})
|
||||
if intVal(ids[0]) != tagIDs["缺陷"] {
|
||||
t.Fatalf("tag: got %v (type %T), want %v", ids[0], ids[0], tagIDs["缺陷"])
|
||||
if intVal(ids[0]) != testTags["缺陷"] {
|
||||
t.Fatalf("tag: got %v (type %T), want %v", ids[0], ids[0], testTags["缺陷"])
|
||||
}
|
||||
} else {
|
||||
t.Fatal("missing issue_tag_ids")
|
||||
|
|
@ -557,23 +609,23 @@ func TestParsePriorityInvalid(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseLabelValid(t *testing.T) {
|
||||
id, err := parseLabel("缺陷")
|
||||
id, err := parseLabel("缺陷", testTags)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if id == 0 {
|
||||
t.Fatal("expected non-zero tag ID")
|
||||
if id != testTags["缺陷"] {
|
||||
t.Fatalf("got %d, want %d", id, testTags["缺陷"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLabelInvalid(t *testing.T) {
|
||||
if _, err := parseLabel("不存在的标签"); err == nil {
|
||||
if _, err := parseLabel("不存在的标签", testTags); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLabelNumeric(t *testing.T) {
|
||||
id, err := parseLabel("999")
|
||||
id, err := parseLabel("999", testTags)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -583,7 +635,7 @@ func TestParseLabelNumeric(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLabelNamesReturnsAll(t *testing.T) {
|
||||
names := labelNames()
|
||||
names := labelNames(testTags)
|
||||
if !strings.Contains(names, "缺陷") {
|
||||
t.Fatal("missing 缺陷 in label names")
|
||||
}
|
||||
|
|
@ -597,7 +649,7 @@ func TestLabelNamesReturnsAll(t *testing.T) {
|
|||
func TestBuildCreateBody_DefaultStatus(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{Owner: "o", Repo: "r", Args: map[string]string{}}
|
||||
input := createIssueInput{Title: "t", Status: ""}
|
||||
body := buildCreateBody(ctx, input, "")
|
||||
body := buildCreateBody(ctx, input, "", testTags)
|
||||
if intVal(body["status_id"]) != 1 {
|
||||
t.Fatalf("default status_id: got %v (type %T), want 1", body["status_id"], body["status_id"])
|
||||
}
|
||||
|
|
@ -606,7 +658,7 @@ func TestBuildCreateBody_DefaultStatus(t *testing.T) {
|
|||
func TestBuildCreateBody_ClosedStatus(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{Owner: "o", Repo: "r", Args: map[string]string{}}
|
||||
input := createIssueInput{Title: "t", Status: "closed"}
|
||||
body := buildCreateBody(ctx, input, "")
|
||||
body := buildCreateBody(ctx, input, "", testTags)
|
||||
if intVal(body["status_id"]) != 5 {
|
||||
t.Fatalf("closed status_id: got %v (type %T), want 5", body["status_id"], body["status_id"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issues", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 Issue 列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -66,7 +66,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
title, err := ctx.RequireArg("title", `--title "Bug: 登录页崩溃"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", v1RepoPath(ctx)+"/issues", body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建 Issue 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -102,13 +102,13 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := ctx.RequireArg("number", "--number 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("查看 Issue 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -123,7 +123,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := ctx.RequireArg("number", "--number 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("关闭 Issue 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -157,7 +157,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := ctx.RequireArg("number", "--number 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("更新 Issue 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -208,11 +208,11 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
number, err := ctx.RequireArg("number", "--number 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := ctx.RequireArg("body")
|
||||
body, err := ctx.RequireArg("body", `--body "可以这样复现..."`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/issues/%s/journals", v1RepoPath(ctx), number), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("添加 Issue 评论失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -232,7 +232,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
func fetchExistingIssue(ctx *common.RuntimeContext, number string) (*existingIssue, error) {
|
||||
getEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("获取 Issue 信息失败: %w", err)
|
||||
}
|
||||
issueData, ok := getEnv.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/organizations", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取组织列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -34,11 +34,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "id", Short: "i", Usage: "Organization ID or login", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("/organizations/%s", id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id my-org")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("/organizations/%s", id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查看组织失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -51,13 +54,16 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
id, _ := ctx.RequireArg("id")
|
||||
id, err := ctx.RequireArg("id", "--id my-org")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/organizations/%s/organization_users", id), q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取组织成员失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -70,7 +76,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "description", Short: "d", Usage: "Description"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
name, _ := ctx.RequireArg("name")
|
||||
name, err := ctx.RequireArg("name", `--name "My Organization"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"name": name,
|
||||
}
|
||||
|
|
@ -79,7 +88,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", "/organizations", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建组织失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/pulls", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 PR 列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -48,8 +48,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, _ := ctx.RequireArg("title")
|
||||
head, _ := ctx.RequireArg("head")
|
||||
title, err := ctx.RequireArg("title", `--title "Fix login crash"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
head, err := ctx.RequireArg("head", `--head feat/new-login`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base := ctx.Arg("base")
|
||||
if base == "" {
|
||||
base = "master"
|
||||
|
|
@ -64,7 +70,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/pulls", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建 PR 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -79,11 +85,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s", ctx.RepoPath(), id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查看 PR 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -98,7 +107,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
method := ctx.Arg("method")
|
||||
if method == "" {
|
||||
method = "merge"
|
||||
|
|
@ -108,7 +120,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/pulls/%s/pr_merge", ctx.RepoPath(), id), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("合并 PR 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -123,11 +135,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/pulls/%s/refuse_merge", ctx.RepoPath(), id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/pulls/%s/refuse_merge", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("关闭 PR 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -141,11 +156,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s/files", ctx.RepoPath(), id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s/files", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 PR 文件列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -159,11 +177,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s/files", ctx.RepoPath(), id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s/files", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 PR diff 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -178,12 +199,18 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
body, _ := ctx.RequireArg("body")
|
||||
id, err := ctx.RequireArg("id", "--id 42")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := ctx.RequireArg("body", `--body "Looks good to me"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/pulls/%s", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch PR: %w", err)
|
||||
return fmt.Errorf("获取 PR 信息失败: %w", err)
|
||||
}
|
||||
issueID, err := extractIssueID(prEnv)
|
||||
if err != nil {
|
||||
|
|
@ -195,7 +222,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1/%s/%s/issues/%d/journals", ctx.Owner, ctx.Repo, issueID), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("添加 PR 评论失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/releases", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 Release 列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -45,8 +45,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
tag, _ := ctx.RequireArg("tag")
|
||||
name, _ := ctx.RequireArg("name")
|
||||
tag, err := ctx.RequireArg("tag", "--tag v1.0.0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name", `--name "Version 1.0.0"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"tag_name": tag,
|
||||
"name": name,
|
||||
|
|
@ -62,7 +68,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/releases", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建 Release 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -77,11 +83,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
|
||||
id, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查看 Release 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
|
|
@ -95,7 +104,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := ctx.RequireArg("id")
|
||||
id, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, delErr := ctx.CallAPI("DELETE", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
|
||||
if delErr != nil {
|
||||
// GitLink API bug: delete succeeds but returns error status.
|
||||
|
|
@ -108,7 +120,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}, nil))
|
||||
}
|
||||
// Release still exists — delete truly failed
|
||||
return delErr
|
||||
return fmt.Errorf("删除 Release 失败: %w", delErr)
|
||||
}
|
||||
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
|
||||
"message": "删除成功",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", path, q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取仓库列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -49,7 +49,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("GET", ctx.RepoPath(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("查看仓库失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -63,14 +63,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "private", Usage: "Make repository private (true/false)", Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
name, err := ctx.RequireArg("name")
|
||||
name, err := ctx.RequireArg("name", `--name "my-project"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get current user login for the create path
|
||||
userEnv, err := ctx.CallAPI("GET", "/users/me", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current user: %w", err)
|
||||
return fmt.Errorf("获取当前用户信息失败: %w", err)
|
||||
}
|
||||
userData, _ := userEnv.Data.(map[string]interface{})
|
||||
login, _ := userData["login"].(string)
|
||||
|
|
@ -91,7 +91,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("/%s/%s", login, name), body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建仓库失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -105,7 +105,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/forks", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Fork 仓库失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -119,7 +119,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
}
|
||||
env, err := ctx.CallAPI("DELETE", ctx.RepoPath(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("删除仓库失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
|
|
@ -17,14 +18,17 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
keyword, _ := ctx.RequireArg("keyword")
|
||||
keyword, err := ctx.RequireArg("keyword", "--keyword my-project")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("search", keyword)
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/projects", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("搜索仓库失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -38,14 +42,17 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
keyword, _ := ctx.RequireArg("keyword")
|
||||
keyword, err := ctx.RequireArg("keyword", "--keyword zhangsan")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("search", keyword)
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/users/list", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("搜索用户失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
Run: func(ctx *common.RuntimeContext) error {
|
||||
env, err := ctx.CallAPI("GET", "/users/me", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取当前用户信息失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -26,13 +26,13 @@ func Shortcuts() []*common.Shortcut {
|
|||
{Name: "login", Short: "l", Usage: "User login name", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
login, err := ctx.RequireArg("login")
|
||||
login, err := ctx.RequireArg("login", "--login zhangsan")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("/users/%s", login), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("查看用户信息失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", webhookRepoPath(ctx)+"/webhooks", q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 Webhook 列表失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -92,7 +92,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
return err
|
||||
}
|
||||
|
||||
webhookURL, err := ctx.RequireArg("url")
|
||||
webhookURL, err := ctx.RequireArg("url", "--url https://example.com/hook")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
|
||||
env, err := ctx.CallAPI("POST", webhookRepoPath(ctx)+"/webhooks", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建 Webhook 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -147,7 +147,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
return err
|
||||
}
|
||||
|
||||
webhookID, err := ctx.RequireArg("id")
|
||||
webhookID, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if webhookURL == "" {
|
||||
getEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current webhook info: %w", err)
|
||||
return fmt.Errorf("获取 Webhook 当前信息失败: %w", err)
|
||||
}
|
||||
webhookData, ok := getEnv.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
|
|
@ -201,7 +201,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
|
||||
env, err := ctx.CallAPI("PUT", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("更新 Webhook 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -217,7 +217,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
return err
|
||||
}
|
||||
|
||||
webhookID, err := ctx.RequireArg("id")
|
||||
webhookID, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
"message": "Webhook deleted successfully",
|
||||
}, nil))
|
||||
}
|
||||
return delErr
|
||||
return fmt.Errorf("删除 Webhook 失败: %w", delErr)
|
||||
}
|
||||
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
|
||||
"message": "Webhook deleted successfully",
|
||||
|
|
@ -251,7 +251,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
return err
|
||||
}
|
||||
|
||||
webhookID, err := ctx.RequireArg("id")
|
||||
webhookID, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -263,7 +263,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/webhooks/%s/tests", webhookRepoPath(ctx), webhookID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("测试 Webhook 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -279,14 +279,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
return err
|
||||
}
|
||||
|
||||
webhookID, err := ctx.RequireArg("id")
|
||||
webhookID, err := ctx.RequireArg("id", "--id 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("查看 Webhook 详情失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ func fetchPageContent(ctx *common.RuntimeContext, projectID, pageName string) (s
|
|||
q.Set("pageName", pageName)
|
||||
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("getWiki"), q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("获取 Wiki 页面现有内容失败: %w", err)
|
||||
}
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
|
|
@ -223,7 +223,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("projectId", projectID)
|
||||
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("wikiPages"), q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("获取 Wiki 页面列表失败: %w", err)
|
||||
}
|
||||
cleanWikiList(env)
|
||||
return ctx.Output(env)
|
||||
|
|
@ -239,7 +239,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
q.Set("pageName", title)
|
||||
env, err := callWikiAPIWithQuery(ctx, "GET", wikiPath("getWiki"), q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("查看 Wiki 页面失败: %w", err)
|
||||
}
|
||||
return outputWithDecodedContent(ctx, env)
|
||||
},
|
||||
|
|
@ -272,7 +272,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -300,7 +300,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
|
||||
env, err := callWikiAPI(ctx, "POST", wikiPath("createWiki"), body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("创建 Wiki 页面失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -320,7 +320,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -373,7 +373,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
|
||||
env, err := callWikiAPI(ctx, "PUT", wikiPath("updateWiki"), body)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("更新 Wiki 页面失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
|
|
@ -388,7 +388,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
title, err := ctx.RequireArg("title")
|
||||
title, err := ctx.RequireArg("title", `--title "Home Page"`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -418,7 +418,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
"message": "Wiki page deleted successfully",
|
||||
})
|
||||
}
|
||||
return delErr
|
||||
return fmt.Errorf("删除 Wiki 页面失败: %w", delErr)
|
||||
}
|
||||
return ctx.OutputData(map[string]string{
|
||||
"message": "Wiki page deleted successfully",
|
||||
|
|
|
|||
Loading…
Reference in New Issue