Gokul f4e9f84fc4 refactor(watcher,wrkflw): finish draining god-files into modules (#92)
* refactor(watcher,wrkflw): finish draining god-files into modules

The last round of extractions (event_kind, ignore, paths, setup,
trigger_cache, debouncer, shutdown) already peeled the leaves off
`watcher.rs`, but the orchestration core — the 430-line `run()`
body, the git-state cache, the per-cycle `evaluate_and_execute` —
was still sitting there, interleaved with the struct definitions
and hiding under 2071 lines of mostly-test file. `main.rs` had the
same problem on the CLI side: the `Run` and `Watch` match arms
were long inline blocks, and the `pull_request + no base-branch`
rejection was *literally copy-pasted* between the run arm's
`apply_base_branch` helper and the watch arm's inline block.
Having the same load-bearing error string in two places is not my
idea of redundancy.

Lift the TTL-bounded git-state cache into a new `git_state.rs` as
a proper `GitStateCache` type. The `Mutex<Option<CachedGitState>>`
field on `WorkflowWatcher` was a hint that this wanted its own
home. Move the whole main loop body into a new `reactor.rs`, and
while at it drag `evaluate_and_execute`, `refresh_trigger_cache_async`
and `canonicalize_changed_paths` with it — they're private, nobody
outside the reactor calls them, and leaving them on
`WorkflowWatcher` was just more clutter. Keep a `#[cfg(test)]`
shim for `cached_git_state` because two tests pin staleness
behavior through the method; changing the tests would have been
scope creep.

On the `wrkflw` side, extract `prefilter.rs` (holds
`PrefilterDecision`, `PrefilterRequest`, `run_trigger_prefilter`,
`build_event_context`, `apply_base_branch`,
`effective_strict_filter`, plus the seven prefilter unit tests),
then `run_workflow_cmd.rs` and `watch_cmd.rs` for the two command
bodies. Collapse the duplicated pull_request rejection into a new
shared `validate_event_requires_base_branch` helper so both hosts
render identical diagnostics. That's the whole reason the
prefilter pattern exists — to put the flag-matrix logic in one
testable place — and letting the watch command keep its own copy
was how we got here in the first place.

While at it, fix a silent-skip hole at the top of the reactor
loop. The workflow-rescan failure at the old `watcher.rs:628-637`
was logged at `debug` level, directly under a comment that called
the fallback "the exact silent-skip pattern the rest of this PR
has been plugging." Someone saw the hazard and then muted it.
Bump it to `warning` so a persistently-failing rescan (chmod 000,
flaking NFS mount, missing parent) actually surfaces to the user
instead of leaving them staring at a session-long "0 triggered"
stream. Non-verbose users would otherwise never know their new
workflow file was being ignored.

Numbers: `watcher.rs` 2071 → 1218 (~300 non-test), `main.rs` 2039
→ 937. New files: `git_state.rs` (171), `reactor.rs` (819),
`prefilter.rs` (814), `run_workflow_cmd.rs` (221), `watch_cmd.rs`
(248). Public API unchanged. All 46 watcher tests + 7 prefilter
tests still pass. \`cargo clippy -D warnings\` and \`cargo fmt
--check\` are both clean.

* fix(watcher,wrkflw): address review findings on god-file drain

Three things from the review on the refactor PR, none of them
structural but all three worth fixing before merge.

First, the unified `validate_event_requires_base_branch` helper had
hardcoded "simulating" in both the strict error and the non-strict
warning. Reads fine from `wrkflw run` — the run command *is*
simulating — but `wrkflw watch` is not simulating anything, it's
watching. The old inline watch-arm check used "Watching
pull_request" for a reason, and collapsing both sites onto a
run-flavored literal was a UX regression for watch users. The PR
body also claimed the two sites were "literally copy-pasted"; they
were similar but not textually identical. Reword the helper
host-neutral ("event `pull_request` without --base-branch..."),
drop the "simulating"/"watching" verbs entirely, and pin it with
three new tests: the non-PR passthrough pair, the
`pull_request_target` strict-mode rejection, and a regression pin
that fails the build if either host-specific verb ever sneaks back
in.

Second, the escalation of the per-cycle rescan-failure log from
`debug` to `warning` was the right direction — `debug` was
invisible to non-verbose users staring at a session-long "0
triggered" stream — but the same function wires up
`supervisor_warned_at_threshold` as a one-shot latch right above
the rescan code, and the rescan branch was left unlatched. Under
`chmod 000 .github/workflows` plus active file churn the new
warning would fire on every debounced cycle for the rest of the
session, which is the diagnostic-flood failure mode on the other
side of the silent-skip hole. Same pattern as the supervisor: warn
once per failing spell, reset the latch on the first healthy
rescan, log a matching info line on recovery so the operator sees
the transition.

Third, `SUPERVISOR_WARN_THRESHOLD` and `SUPERVISOR_HARD_CAP` were
declared `pub(crate)` on `watcher.rs` but read only from
`reactor.rs`. Move them next to the loop that enforces them, drag
the compile-time `const _: () = { assert!(...) }` invariant block
along, and leave a breadcrumb in `watcher.rs` so `git blame`
doesn't have to chase it.

46/46 watcher tests pass, prefilter tests go 7 → 10, clippy and
fmt both clean.
2026-04-12 00:14:42 +05:30
2025-08-28 12:56:05 +05:30
2025-04-30 17:57:29 +05:30
2025-04-14 17:00:23 +05:30
2025-03-29 12:58:02 +05:30
2025-08-13 17:57:44 +05:30

WRKFLW

Crates.io License Build Status Downloads

A command-line tool for validating and executing GitHub Actions workflows locally. Test your workflows on your machine before pushing to GitHub.

WRKFLW Demo

Features

  • TUI interface — interactive terminal UI for browsing, running, and monitoring workflows
  • Workflow validation — syntax checks, structural validation, and composite action input cross-checking with CI/CD-friendly exit codes
  • Local execution — run workflows using Docker, Podman, or emulation mode (no containers)
  • Job selection — run individual jobs with --job flag or via TUI job selection mode
  • Job dependency resolution — automatic ordering based on needs with parallel execution of independent jobs
  • Action support — Docker container actions, JavaScript actions, composite actions, and local actions
  • Reusable workflows — execute caller jobs via jobs.<id>.uses (local or owner/repo/path@ref)
  • GitHub context emulation — environment variables, GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STEP_SUMMARY
  • Matrix builds — full support for include, exclude, max-parallel, and fail-fast
  • Secrets management — multiple providers (env, file, Vault, AWS, Azure, GCP) with masking and encryption
  • Remote triggering — trigger workflow_dispatch runs on GitHub or GitLab pipelines
  • GitLab support — validate and trigger GitLab CI pipelines

Installation

cargo install wrkflw

Or build from source:

git clone https://github.com/bahdotsh/wrkflw.git
cd wrkflw
cargo build --release

Quick Start

# Launch the TUI (auto-detects .github/workflows)
wrkflw

# Validate workflows
wrkflw validate

# Run a workflow
wrkflw run .github/workflows/ci.yml

Usage

Validation

# Validate all workflows in .github/workflows
wrkflw validate

# Validate specific files or directories
wrkflw validate path/to/workflow.yml
wrkflw validate path/to/workflows/

# Validate multiple paths
wrkflw validate flow-1.yml flow-2.yml path/to/workflows/

# GitLab pipelines
wrkflw validate .gitlab-ci.yml --gitlab

# Verbose output
wrkflw validate --verbose path/to/workflow.yml

Exit codes: 0 = all valid, 1 = validation failures, 2 = usage error. Use --no-exit-code to disable.

Execution

# Run with Docker (default)
wrkflw run .github/workflows/ci.yml

# Run with Podman
wrkflw run --runtime podman .github/workflows/ci.yml

# Run in emulation mode (no containers)
wrkflw run --runtime emulation .github/workflows/ci.yml

# Run a specific job
wrkflw run --job build .github/workflows/ci.yml

# List jobs in a workflow
wrkflw run --jobs .github/workflows/ci.yml

# Preserve failed containers for debugging
wrkflw run --preserve-containers-on-failure .github/workflows/ci.yml

TUI

# Open TUI with default directory
wrkflw tui

# Open with specific runtime
wrkflw tui --runtime podman

Controls:

Key Action
Tab / 1-4 Switch tabs (Workflows, Execution, Logs, Help)
Up/Down or j/k Navigate
Space Toggle selection
Enter Run / View details
r Run selected workflows
a / n Select all / Deselect all
e Cycle runtime (Docker / Podman / Emulation)
v Toggle Execution / Validation mode
t Trigger remote workflow
q / Esc Quit / Back

Remote Triggering

Trigger workflow_dispatch events on GitHub or GitLab.

# GitHub (requires GITHUB_TOKEN env var)
wrkflw trigger workflow-name --branch main --input key=value

# GitLab (requires GITLAB_TOKEN env var)
wrkflw trigger-gitlab --branch main --variable key=value

Runtime Modes

Mode Description Best for
Docker (default) Full container isolation, closest to GitHub runners Production, CI/CD
Podman Rootless containers, no daemon required Security-conscious environments
Emulation Runs directly on host, no containers needed Quick local testing

Reusable Workflows

jobs:
  call-local:
    uses: ./.github/workflows/shared.yml

  call-remote:
    uses: my-org/my-repo/.github/workflows/shared.yml@v1
    with:
      foo: bar
    secrets:
      token: ${{ secrets.MY_TOKEN }}
  • Local refs resolve relative to the working directory
  • Remote refs are shallow-cloned at the specified @ref
  • with: entries become INPUT_<KEY> env vars; secrets: become SECRET_<KEY>

Limitations: outputs from called workflows are not propagated back; secrets: inherit is not supported; private repos for remote uses: are not yet supported.

Secrets Management

WRKFLW supports GitHub Actions-compatible ${{ secrets.* }} syntax with multiple providers:

# Environment variables (simplest)
export GITHUB_TOKEN="ghp_..."
wrkflw run .github/workflows/ci.yml

# File-based secrets (JSON, YAML, or .env format)
# Configure in ~/.wrkflw/secrets.yml

Supported providers: environment variables, file-based, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager. See the secrets demo for detailed examples.

Limitations

Supported

  • Workflow syntax validation with exit codes
  • Job dependency resolution and parallel execution
  • Matrix builds, environment variables, GitHub context
  • Container, JavaScript, composite, and local actions
  • Reusable workflows (caller jobs)
  • Environment files (GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STEP_SUMMARY)
  • TUI and CLI interfaces
  • Container cleanup (even on Ctrl+C)

Not Supported

  • GitHub encrypted secrets and fine-grained permissions
  • actions/cache (no persistent cache between runs)
  • Artifact upload/download between jobs
  • Event triggers other than workflow_dispatch
  • Windows and macOS runners
  • Job/step timeouts, concurrency, and cancellation
  • Service containers in emulation mode
  • Reusable workflow output propagation (needs.<id>.outputs.*)

Project Structure

WRKFLW is organized as a Cargo workspace with focused crates:

Crate Purpose
wrkflw CLI binary and library entry point
wrkflw-executor Workflow execution engine
wrkflw-parser Workflow file parsing and schema validation
wrkflw-evaluator Structural evaluation of workflow files
wrkflw-validators Validation rules for jobs, steps, triggers
wrkflw-runtime Container and emulation runtime abstractions
wrkflw-ui Terminal user interface
wrkflw-models Shared data structures
wrkflw-matrix Matrix expansion utilities
wrkflw-secrets Secrets management with multiple providers
wrkflw-github GitHub API integration
wrkflw-gitlab GitLab API integration
wrkflw-logging In-memory logging for TUI/CLI
wrkflw-utils Shared helpers

License

MIT License - see LICENSE for details.

Description
Validate and Run GitHub Actions locally.
Readme MIT 29 MiB
Languages
Rust 98.6%
Shell 1.4%