gitlink-cli/shortcuts/release/release.go

234 lines
6.6 KiB
Go

package release
import (
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"github.com/gitlink-org/gitlink-cli/internal/output"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func Shortcuts() []*common.Shortcut {
return []*common.Shortcut{
{
Name: "list",
Description: "List releases",
Flags: []common.Flag{
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return fmt.Errorf("解析仓库信息失败: %w", err)
}
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/releases", q)
if err != nil {
return fmt.Errorf("获取 Release 列表失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "create",
Description: "Create a release",
Flags: []common.Flag{
{Name: "tag", Short: "t", Usage: "Tag name", Required: true},
{Name: "name", Short: "n", Usage: "Release name", Required: true},
{Name: "body", Short: "b", Usage: "Release notes"},
{Name: "target", Usage: "Target branch", Default: "master"},
{Name: "prerelease", Usage: "Mark as prerelease (true/false)", Default: "false"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return fmt.Errorf("解析仓库信息失败: %w", err)
}
tag, err := ctx.RequireArg("tag")
if err != nil {
return err
}
name, err := ctx.RequireArg("name")
if err != nil {
return err
}
payload := map[string]interface{}{
"tag_name": tag,
"name": name,
}
if b := ctx.Arg("body"); b != "" {
payload["body"] = b
}
if t := ctx.Arg("target"); t != "" {
payload["target_commitish"] = t
}
if ctx.Arg("prerelease") == "true" {
payload["prerelease"] = true
}
env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/releases", payload)
if err != nil {
return fmt.Errorf("创建 Release 失败: %w", err)
}
return ctx.Output(env)
},
},
{
Name: "view",
Description: "View release details",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Release ID or tag", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return fmt.Errorf("解析仓库信息失败: %w", err)
}
id, err := ctx.RequireArg("id")
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)
},
},
{
Name: "download",
Description: "Download release assets",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Release ID or tag", Required: true},
{Name: "asset", Short: "a", Usage: "Asset file name (omit to list all assets)"},
{Name: "dir", Short: "d", Usage: "Download directory", Default: "."},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return fmt.Errorf("解析仓库信息失败: %w", err)
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
assetName := ctx.Arg("asset")
dlDir := ctx.Arg("dir")
if dlDir == "" {
dlDir = "."
}
releaseEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
if err != nil {
return fmt.Errorf("获取 Release 信息失败: %w", err)
}
releaseData, ok := releaseEnv.Data.(map[string]interface{})
if !ok {
return fmt.Errorf("Release 响应格式异常")
}
assets, ok := releaseData["assets"].([]interface{})
if !ok || len(assets) == 0 {
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
"message": "该 Release 没有附件资源",
}, nil))
}
if assetName == "" {
var assetList []map[string]interface{}
for _, a := range assets {
asset, _ := a.(map[string]interface{})
assetList = append(assetList, map[string]interface{}{
"name": asset["name"],
"size": asset["size"],
})
}
return ctx.OutputData(map[string]interface{}{
"assets": assetList,
})
}
var downloadURL string
for _, a := range assets {
asset, _ := a.(map[string]interface{})
name, _ := asset["name"].(string)
if name == assetName {
downloadURL, _ = asset["download_url"].(string)
if downloadURL == "" {
downloadURL, _ = asset["url"].(string)
}
break
}
}
if downloadURL == "" {
return fmt.Errorf("Release 中未找到附件 %q", assetName)
}
if err := os.MkdirAll(dlDir, 0755); err != nil {
return fmt.Errorf("创建目录 %s 失败: %w", dlDir, err)
}
resp, err := ctx.Client.HTTP.Get(downloadURL)
if err != nil {
return fmt.Errorf("下载失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("下载失败: HTTP %d", resp.StatusCode)
}
destPath := filepath.Join(dlDir, assetName)
file, err := os.Create(destPath)
if err != nil {
return fmt.Errorf("创建文件 %s 失败: %w", destPath, err)
}
defer file.Close()
written, err := io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
"message": "下载完成",
"path": destPath,
"size": written,
}, nil))
},
},
{
Name: "delete",
Description: "Delete a release",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Release ID", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return fmt.Errorf("解析仓库信息失败: %w", err)
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
_, delErr := ctx.CallAPI("DELETE", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
if delErr != nil {
_, viewErr := ctx.CallAPI("GET", fmt.Sprintf("%s/releases/%s", ctx.RepoPath(), id), nil)
if viewErr != nil {
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
"message": "删除成功",
}, nil))
}
return fmt.Errorf("删除 Release 失败: %w", delErr)
}
return ctx.Output(output.SuccessEnvelope(map[string]interface{}{
"message": "删除成功",
}, nil))
},
},
}
}