feat(run): show runs like gh, watch them live, and download artifacts

run list prints a table with a status icon, the workflow name, title,
branch, event, elapsed time and age, and the total from the server; runs
carry workflow, workflow_path and run_number in JSON. run view shows the
run's state and trigger, its jobs with their steps and durations, the
next steps and the run's URL; --json includes the jobs, --log prints
their logs and --web opens the run.

Run commands accept #N and run URLs, and ask for a recent run in a
terminal when the argument is left out. run watch redraws the run on the
alternate screen, then prints it with a summary; an expired --timeout
names the run and suspects a missing runner when no job started.

run cancel no longer asks for confirmation, rerun refuses unfinished runs
up front, reruns a single job with --job and reports the new attempt,
and delete names the run in its prompt. run logs follows several jobs
with --follow, prefixing lines with the job's name, selects jobs by name
or with --failed, and downloads the archive with --output or --dir.
run download extracts the artifacts of a run by name, and artifact list
shows sizes and expiry.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 23:46:26 +02:00
1 parent b09c1aab4a
commit 9f69a89537
19 files changed
+2636 -646

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package terminal
import (
"context"
"errors"
"charm.land/huh/v2"
)
// Choice is one option of a selection prompt: Label is shown and Value returned.
type Choice struct {
Label, Value string
}
// Chooser asks for one of several options. FormPrompter implements it; a Prompter without it
// cannot offer a selection.
type Chooser interface {
// Choose asks for one of choices, starting at the one whose value is initial, else the first.
Choose(ctx context.Context, title string, choices []Choice, initial string) (string, error)
}
// Choose asks on stderr for one of choices and returns its value.
func (p *FormPrompter) Choose(ctx context.Context, title string, choices []Choice, initial string) (string, error) {
if len(choices) == 0 {
return "", errors.New("nothing to choose from")
}
options := make([]huh.Option[string], len(choices))
value := choices[0].Value
for i, choice := range choices {
options[i] = huh.NewOption(SafeLine(choice.Label), choice.Value)
if choice.Value == initial {
value = initial
}
}
err := p.runForm(ctx, huh.NewForm(huh.NewGroup(huh.NewSelect[string]().Title(title).Options(options...).Height(min(len(choices), 10)+2).Value(&value))))
return value, err
}