mirror of
https://github.com/bahdotsh/wrkflw.git
synced 2026-07-09 20:09:35 +02:00
The execution tab used to stare back at you with a single progress bar while the executor did... whatever it was doing. You found out what happened when the whole workflow was over, and only then. Not great for a tool whose entire point is running workflows locally. The reason was structural. `execute_workflow` handed back one `Vec<JobResult>` at the very end, `StepStatus` only had terminal variants (Success/Failure/Skipped — no Running, no Pending), and the container runtimes read logs *after* the container exited via `wait_container` followed by `logs().collect()`. The TUI was doing the best it could with a request/response it couldn't observe. So: thread an optional event stream through the executor. New `ExecutionEvent` enum (`JobStarted`, `StepStarted`, `StepLogChunk`, `StepCompleted`, `JobCompleted`, plus `Execution*` bookends) emitted via an `EventSink` that bundles an unbounded sender with a per-run `JobId` allocator so matrix combinations get distinct ids. The TUI spawns a receiver, drains it each tick, and paints the detail view live — spinner on the running step, ✓/✖/⊘/○ everywhere else, per-step duration, and a bottom pane that tails the step's log buffer as bytes come off stdout/stderr. `f` re-engages auto-follow; any manual j/k pins you where you are. Log streaming required rewriting all four runtimes. Emulation, secure_emulation and podman move to `tokio::process::Command` with piped stdio drained via `AsyncBufReadExt::lines()` in parallel tasks. Docker is the fun one: `logs(follow: true)` now runs concurrently with `wait_container` under `tokio::join!`, instead of the old sequential wait-then-read. Chunks mirror into the sink *and* accumulate into the final `ContainerOutput` so both the live view and the post-mortem `StepResult.output` stay truthful. CLI path is unchanged — `ExecutionConfig.event_sink` defaults to `None` and the whole thing short-circuits. Reusable workflows and composite actions collapse to a single synthetic step in the event stream for v1; nested forwarding is a separate problem for another day. `StepStatus` gained `Pending` and `Running` variants with `#[non_exhaustive]` so we can add more without breaking callers again. Per-step log buffer is capped at 10k lines with a truncation marker; the Step Output pane pins to the bottom so newest-first is always visible without caring about scroll state. Two new integration tests assert event ordering (JobStarted before any of its StepStarted, StepCompleted before JobCompleted, pairs match by (job_id, step_idx)) and that \`StepLogChunk\` arrives *between* \`StepStarted\` and \`StepCompleted\`. The sink-absent path stays behaviorally identical. Please don't put back the "collect everything then show it" pattern when the executor grows new capabilities. The streaming seams are there now; use them.
wrkflw-executor
The execution engine that runs GitHub Actions workflows locally (Docker, Podman, or emulation).
- Job graph execution with
needsordering and parallel independent jobs - Docker/Podman container steps and emulation mode
- Run individual jobs via
target_job/--jobflag - GitHub Actions environment file support (
GITHUB_OUTPUT,GITHUB_ENV,GITHUB_PATH,GITHUB_STEP_SUMMARY) with read-back - Docker-based action resolution (container, JavaScript, composite, local)
- Job-level
container:directive support - Used by:
wrkflwCLI and TUI
API sketch
use wrkflw_executor::{execute_workflow, ExecutionConfig, RuntimeType};
let cfg = ExecutionConfig {
runtime: RuntimeType::Docker,
verbose: true,
preserve_containers_on_failure: false,
target_job: Some("build".to_string()), // run a single job
};
let workflow_path = std::path::Path::new(".github/workflows/ci.yml");
let result = execute_workflow(workflow_path, cfg).await?;
println!("workflow status: {:?}", result.summary_status);
Prefer using the wrkflw binary for a complete UX across validation, execution, and logs.