gitlink-cli/shortcuts/ci/ci.go

385 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ci
import (
"fmt"
"net/url"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func Shortcuts() []*common.Shortcut {
return []*common.Shortcut{
// +builds列出 CI 构建记录
{
Name: "builds",
Description: "列出 CI 构建记录",
Long: `List CI/CD builds for the current repository.
Shows build number, status, branch, event type, and duration.
Use --status, --branch, --event, --since, --until for filtering,
and --page and --limit for pagination.`,
Example: ` # List recent builds
gitlink ci +builds
# Show 50 builds
gitlink ci +builds --limit 50
# Filter by status
gitlink ci +builds --status success
gitlink ci +builds -s failure
# Filter by branch
gitlink ci +builds --branch main
gitlink ci +builds -b develop
# Filter by trigger event
gitlink ci +builds --event push
gitlink ci +builds --event pull_request
# Filter by time range
gitlink ci +builds --since 1700000000 --until 1700086400`,
Flags: []common.Flag{
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
{Name: "status", Short: "s", Usage: "Filter by build status (success, failure, running)"},
{Name: "branch", Short: "b", Usage: "Filter by branch"},
{Name: "event", Usage: "Filter by trigger event (push, pull_request, tag)"},
{Name: "since", Usage: "Start time (UNIX timestamp)"},
{Name: "until", Usage: "End time (UNIX timestamp)"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
if s := ctx.Arg("status"); s != "" {
q.Set("status", s)
}
if b := ctx.Arg("branch"); b != "" {
q.Set("branch", b)
}
if e := ctx.Arg("event"); e != "" {
q.Set("event", e)
}
if s := ctx.Arg("since"); s != "" {
q.Set("since", s)
}
if u := ctx.Arg("until"); u != "" {
q.Set("until", u)
}
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/builds", q)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +logs查看构建日志
{
Name: "logs",
Description: "查看构建日志",
Long: `View logs for a specific CI/CD build step.
Requires the build number. Use --stage and --step to navigate
to a particular stage and step within the build.`,
Example: ` # View logs for build #42
gitlink ci +logs --build 42
# View logs for stage 2, step 3 of build #42
gitlink ci +logs --build 42 --stage 2 --step 3`,
Flags: []common.Flag{
{Name: "build", Short: "b", Usage: "Build number", Required: true},
{Name: "stage", Short: "s", Usage: "Stage number", Default: "1"},
{Name: "step", Usage: "Step number", Default: "1"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
build, _ := ctx.RequireArg("build")
stage := ctx.Arg("stage")
step := ctx.Arg("step")
if stage == "" {
stage = "1"
}
if step == "" {
step = "1"
}
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 ctx.Output(env)
},
},
// +restart重新触发构建
{
Name: "restart",
Description: "重新触发构建",
Long: `Restart a previously completed or failed CI/CD build.
The build will be re-triggered with the same commit and configuration.`,
Example: ` # Restart build #42
gitlink ci +restart --build 42`,
Flags: []common.Flag{
{Name: "build", Short: "b", Usage: "Build number", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
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)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +stop停止构建
{
Name: "stop",
Description: "停止构建",
Long: `Stop a currently running CI/CD build.
Prompts for confirmation before stopping the build.`,
Example: ` # Stop build #42
gitlink ci +stop --build 42`,
Flags: []common.Flag{
{Name: "build", Short: "b", Usage: "Build number", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
build, _ := ctx.RequireArg("build")
if err := common.ConfirmAction(
fmt.Sprintf("stop build #%s", build),
); err != nil {
return err
}
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("%s/builds/%s/stop", ctx.RepoPath(), build), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// --- Pipeline / Actions group ---
// +pipelines列出 CI 流水线模板
{
Name: "pipelines",
Description: "列出 CI 流水线模板",
Run: func(ctx *common.RuntimeContext) error {
env, err := ctx.CallAPI("GET", "/pm/pipelines", nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +run-pipeline运行 CI 流水线
{
Name: "run-pipeline",
Description: "运行 CI 流水线",
Flags: []common.Flag{
{Name: "pipeline", Short: "p", Usage: "Pipeline name or ID", Required: true},
{Name: "branch", Short: "b", Usage: "Branch to run on"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
pipeline, err := ctx.RequireArg("pipeline")
if err != nil {
return err
}
body := map[string]interface{}{
"pipeline": pipeline,
}
if branch := ctx.Arg("branch"); branch != "" {
body["branch"] = branch
}
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/actions/runs", ctx.RepoPath()), body)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +runs列出 CI 运行历史
{
Name: "runs",
Description: "列出 CI 运行历史",
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 err
}
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/actions/runs", ctx.RepoPath()), q)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +save-yaml保存流水线 YAML 配置
{
Name: "save-yaml",
Description: "保存流水线 YAML 配置",
Flags: []common.Flag{
{Name: "yaml", Short: "y", Usage: "YAML content", Required: true},
{Name: "name", Short: "n", Usage: "Pipeline name"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
yamlContent, err := ctx.RequireArg("yaml")
if err != nil {
return err
}
body := map[string]interface{}{
"yaml": yamlContent,
}
if name := ctx.Arg("name"); name != "" {
body["name"] = name
}
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/pipelines/save_yaml", ctx.RepoPath()), body)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +pipeline-detail查看流水线详情
{
Name: "pipeline-detail",
Description: "查看流水线详情",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
env, err := ctx.CallAPI("GET", fmt.Sprintf("/v1%s/pipelines/%s", ctx.RepoPath(), id), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +delete-pipeline删除流水线
{
Name: "delete-pipeline",
Description: "删除流水线",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
id, err := ctx.RequireArg("id")
if err != nil {
return err
}
env, err := ctx.CallAPI("DELETE", fmt.Sprintf("/v1%s/pipelines/%s", ctx.RepoPath(), id), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +disable为仓库禁用 CI
{
Name: "disable",
Description: "为仓库禁用 CI",
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/actions/disable", ctx.RepoPath()), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +enable为仓库启用 CI
{
Name: "enable",
Description: "为仓库启用 CI",
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/actions/enable", ctx.RepoPath()), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +run-log获取 CI 运行日志
{
Name: "run-log",
Description: "获取 CI 运行日志",
Flags: []common.Flag{
{Name: "run", Short: "r", Usage: "Run ID", Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
run, err := ctx.RequireArg("run")
if err != nil {
return err
}
env, err := ctx.CallAPI("POST", fmt.Sprintf("/v1%s/actions/runs/%s/jobs/0", ctx.RepoPath(), run), nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
// +run-results获取 CI 运行结果报告
{
Name: "run-results",
Description: "获取 CI 运行结果报告",
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 err
}
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/pipelines/run_results", ctx.RepoPath()), q)
if err != nil {
return err
}
return ctx.Output(env)
},
},
}
}