hive-forge: add ci-runs listing verb, fix ci-log's ambiguous no-log message

This commit is contained in:
damocles 2026-09-02 02:10:50 +02:00 committed by mara
commit 56c0602c2f
6 changed files with 204 additions and 59 deletions

View file

@ -0,0 +1,78 @@
//! `ci-runs [--workflow <file>] [--branch <name>] [--limit N] [--page N]` —
//! list a repo's CI Actions runs, newest first.
//!
//! A `ci-rerun` dispatch creates a `workflow_dispatch` run that publishes
//! no commit status, so there was previously no way from the CLI to see
//! what it actually created — or to discover a real run number to hand
//! `ci-log`/`artifact-get` at all, short of guessing near a known one.
//! This lists runs directly; `--workflow`/`--branch` filter server-side
//! via `ListActionRunsQuery`, not a client-side scan.
use anyhow::Result;
use clap::Args as ClapArgs;
use forgejo_api::structs::{ActionRun, ListActionRunsQuery};
use super::ci_common::run_number_of;
use crate::client::Client;
use crate::verbs::print_json;
#[derive(ClapArgs)]
pub struct Args {
/// Only runs of this workflow file (e.g. `ci.yml`).
#[arg(long)]
workflow: Option<String>,
/// Only runs on this branch or ref (e.g. `main`, `damocles/foo`).
#[arg(long)]
branch: Option<String>,
/// How many runs to print (default 20).
#[arg(long, default_value_t = 20, value_parser = clap::value_parser!(u64).range(1..))]
limit: u64,
/// Page number (1-based, default 1). Combine with `--limit` to page
/// through further back than the default window.
#[arg(long, default_value_t = 1, value_parser = clap::value_parser!(u64).range(1..))]
page: u64,
}
/// # Errors
///
/// Returns an error if the list request fails (network, or a non-2xx).
pub fn run(client: &Client, args: Args) -> Result<()> {
let (owner, name) = client.owner_repo()?;
let query = ListActionRunsQuery {
workflow_id: args.workflow.clone(),
r#ref: args.branch.clone(),
..Default::default()
};
let body = client
.api()
.list_action_runs(owner, name, query)
.page(u32::try_from(args.page).unwrap_or(u32::MAX))
.page_size(u32::try_from(args.limit).unwrap_or(u32::MAX))
.send()?;
let runs = body.workflow_runs.unwrap_or_default();
if client.json_mode() {
return print_json(&serde_json::to_value(&runs)?);
}
if runs.is_empty() {
println!("no matching runs in {}", client.repo());
return Ok(());
}
for run in &runs {
print_row(run);
}
Ok(())
}
/// One human-readable line per run: number, status, workflow file, ref,
/// title. Widths are cosmetic alignment, not a fixed schema — a longer
/// value just pushes the next column over rather than truncating.
fn print_row(run: &ActionRun) {
let number = run_number_of(run).map_or_else(|| "?".to_owned(), |n| format!("#{n}"));
let status = run.status.as_deref().unwrap_or("unknown");
let workflow = run.workflow_id.as_deref().unwrap_or("?");
let run_ref = run.prettyref.as_deref().unwrap_or("?");
let title = run.title.as_deref().unwrap_or("");
println!("{number:<7} {status:<10} {workflow:<20} {run_ref:<28} {title}");
}