feat(action): add +logs for raw workflow job logs

Wraps GET /actions/runs/:run_id/jobs/:job/logs (runs#job_logs, which
redirects to the log file). Non-JSON body is printed as-is so the
output pipes cleanly into grep/less.

Production-verified on gitlink.org.cn (real run's job log streamed).
Unit test covers the nested path and the --run-id integer guard.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1os21ka23r9navae6mrro 2026-07-08 14:48:50 +00:00
parent 05a1859c63
commit a31c53484e
5 changed files with 55 additions and 0 deletions

View File

@ -502,6 +502,9 @@ gitlink-cli action +run --owner Gitlink --repo forgeplus -w ci.yml -r master
gitlink-cli action +rerun --owner Gitlink --repo forgeplus -i 6
gitlink-cli action +job-rerun --owner Gitlink --repo forgeplus -i 6 -j build
# Raw logs of a workflow job
gitlink-cli action +logs --owner Gitlink --repo forgeplus -i 6 -j 0
# Enable or disable a workflow
gitlink-cli action +disable --owner Gitlink --repo forgeplus -w ci.yml
gitlink-cli action +enable --owner Gitlink --repo forgeplus -w ci.yml

View File

@ -3,6 +3,7 @@
"cmd.action.enable.short": "Enable Actions for the repository",
"cmd.action.job_rerun.short": "Rerun a single job of a run",
"cmd.action.list.short": "List workflow files (.gitea/workflows)",
"cmd.action.logs.short": "Fetch the raw logs of a workflow job",
"cmd.action.rerun.short": "Rerun all jobs of a run",
"cmd.action.run.short": "Trigger a workflow run",
"cmd.action.runs.short": "List runs of a workflow",

View File

@ -3,6 +3,7 @@
"cmd.action.enable.short": "为仓库启用 Actions",
"cmd.action.job_rerun.short": "重跑运行中的单个任务",
"cmd.action.list.short": "列出工作流文件(.gitea/workflows",
"cmd.action.logs.short": "获取流水线任务的原始日志",
"cmd.action.rerun.short": "重跑一次运行的全部任务",
"cmd.action.run.short": "触发一次工作流运行",
"cmd.action.runs.short": "列出工作流的运行记录",

View File

@ -146,6 +146,36 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
return ctx.Output(env)
},
},
{
Name: "logs",
Description: tr.T("cmd.action.logs.short"),
Flags: []common.Flag{
{Name: "run-id", Short: "i", Usage: tr.T("flag.action.run_id"), Required: true},
{Name: "job", Short: "j", Usage: tr.T("flag.action.job"), Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
runID, err := requireIntArg(ctx, "run-id")
if err != nil {
return err
}
job, err := ctx.RequireArg("job")
if err != nil {
return err
}
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/runs/%s/jobs/%s/logs", actionsPath(ctx), runID, url.PathEscape(job)), nil)
if err != nil {
return err
}
if text, ok := env.Data.(string); ok {
fmt.Print(text)
return nil
}
return ctx.Output(env)
},
},
{
Name: "enable",
Description: tr.T("cmd.action.enable.short"),

View File

@ -2,6 +2,7 @@ package action
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@ -157,3 +158,22 @@ func TestActionEnableDisableEndpoints(t *testing.T) {
}
}
}
func TestActionLogsBuildsNestedLogsPath(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/actions/runs/6/jobs/0/logs.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "log line 1\nlog line 2\n")
}))
defer server.Close()
if err := runShortcut(t, server, "logs", map[string]string{"run-id": "6", "job": "0"}); err != nil {
t.Fatalf("logs failed: %v", err)
}
if err := runShortcut(t, server, "logs", map[string]string{"run-id": "abc", "job": "0"}); err == nil {
t.Fatal("expected error for non-integer --run-id")
}
}