ghafmt
Version updated for https://github.com/jonathanrainer/ghafmt to version v0.1.0.
- This action is used across all versions by ? repositories.
Action Type
This is a Composite action.
Go to the GitHub Marketplace to find the latest changes.
Action Summary
ghafmt is a formatting tool designed to standardize GitHub Actions YAML files, including workflows and action metadata (action.yml/action.yaml). It automates the process of reordering keys, sorting blocks alphabetically, enforcing consistent spacing, and converting identifiers to snake_case, thereby reducing formatting inconsistencies and improving code readability. This action helps streamline code reviews by ensuring that diffs focus on meaningful changes rather than stylistic differences.
Release notes
Features
Action metadata file formatting
ghafmt now detects and formats GitHub Actions action metadata files
(action.yml / action.yaml) in addition to workflow files.
Supported action types
- Composite actions (
runs.using: composite) — top-level key ordering,runskey ordering, step key ordering, alphabeticalwithsorting inside steps, alphabeticalinputs/outputssorting with idiomatic per-entry key ordering,brandingkey ordering, and snake_case enforcement on step IDs - JavaScript actions (
runs.using: node20/node24) — top-level key ordering,runskey ordering (using → pre → pre-if → main → post → post-if), alphabeticalinputs/outputssorting - Docker container actions (
runs.using: docker) — top-level key ordering,runskey ordering (using → image → args → env → pre-entrypoint → entrypoint → post-entrypoint), alphabeticalinputs/outputssorting
Files that cannot be identified as a workflow or action pass through with presentation transforms only (blank lines, variable spacing).
Cross-platform binary builds
Pre-built binaries for aarch64-apple-darwin and x86_64-unknown-linux-musl
are now attached to each GitHub Release as assets. Support for
aarch64-unknown-linux-musl and x86_64-pc-windows-msvc is planned for
future releases. (247bd28)
Docker image published to GHCR
ghafmt is now available as a Docker image on GitHub Container Registry:
docker pull ghcr.io/jonathanrainer/ghafmt:latest
Nightly builds are also published as ghcr.io/jonathanrainer/ghafmt:nightly. (097523e)
GitHub Action
ghafmt is now available as a composite GitHub Action:
- uses: jonathanrainer/ghafmt@v0.2.0
with:
path: .github/workflows/
mode: check
Supports Linux x86_64, Linux ARM64, and macOS ARM64 and Intel. (90ca03b)
macOS Intel binary builds
Pre-built binaries for x86_64-apple-darwin (macOS Intel) are now included in
each GitHub Release, cross-compiled from the ARM64 runner using Apple’s clang. (839d6cb)
macOS binary signing and notarization
Released macOS binaries are now signed with a Developer ID Application certificate and notarized with Apple, so Gatekeeper no longer blocks them on download.
Signing and notarization is implemented as a composite action
(.github/actions/sign-and-notarize) following the existing set-cargo-version pattern, and
is called from the build artifacts workflow for macOS targets only. It runs exclusively on main
so signing credentials are never exposed during PR or feature branch builds. (638ec79)
Refactor: split main.rs, improve CLI, harden stdin reading
Structural changes
main.rshas been split into focused modules:src/lib.rs— orchestration andFormatterResultsrc/cli.rs— Clap argument definitionssrc/commands/— one file per mode (check,format,write,list), sharing aCommandtraitsrc/fs.rs—expand_paths,atomic_write, stdin reading
Bug fixes
read_from_stdinpreviously usedtake(LIMIT)to cap reads but then checkedn > LIMIT, a guard that could never fire. The function now probes for an overflow byte after reading, correctly detecting inputs that exceed the limit while accepting files of exactly the limit size.
Dependencies
- Added
atomic-write-file— replaces the hand-rolled temp-file-then-rename implementation ofatomic_write. - Added
patharg— typedInputArgenum wrapping file paths and stdin (-). - Added
strum_macros— derive-based enum string conversions for CLI mode/colour variants.
Tests
- Added unit tests for
read_with_limit(under limit, exactly at limit, over limit) andatomic_write(overwrites rather than appends). (809abb2)
Fixes
Acknowledgements, licence detection fix & changeset detection
Added an Acknowledgements section to the README crediting @pantoniou for libfyaml and @0k for the fyaml Rust bindings.
Added a LICENSE symlink pointing to LICENSE-MIT so that GitHub’s licence detector
correctly identifies the repository licence rather than reporting it as unknown.
Updated the pre-commit hooks to detect if a changeset exists on the branch, just to save time forgetting one on PRs (3f8595e)
Pre-commit hooks
A .pre-commit-config.yaml is now provided for local development. It runs
clippy, cargo deny, actionlint, and ghafmt (from source) as opt-in pre-commit
checks. (ce899c8)
Add README, CONTRIBUTING, and MIT/Apache-2.0 dual license
Adds a README with usage examples, CI integration snippets, and a before/after example. Adds CONTRIBUTING.md with dev setup and transformer guide. Switches license from GPL-3.0-only to MIT OR Apache-2.0. (490bf69)
Bring in libfyaml comment-handling fixes and related Rust improvements
Integrates fixes from the restructured jonathanrainer/libfyaml fork and
adds the Rust-side presentation work that depends on them.
Features
- Add
TopLevelCommentSpacerpresentation transformer: inserts a blank line before any block of standalone top-level comments (col 0) that follows content at a deeper indentation level, covering end-of-file comments and other top-level comment blocks not preceded by a known top-level key
Bug fixes
insert_blank_line_before_comment_blockgains amax_comment_indentparameter; when set, a comment preceded by an indent deeper than the threshold is treated as content rather than part of the preceding comment block, preventing the scan from skipping deeply-nested comments that belong to the previous section- Fix
TopLevelCommentSpacernot triggering on inline right-hand comments: only inserts a blank line when the preceding event is aLinebreak, so comments that appear on the same line as a value are correctly ignored (7d783e2)
Code review fixes and improvements
Address findings from a review of the codebase.
Bug fixes
- Fix typo in
FilterSorter:on/pull_request_targe/branches-ignore→on/pull_request_target/branches-ignore, which was silently skipping that sort path - Remove
outputsandsecretsfromWorkflowDispatchSorter; those keys belong toworkflow_callonly and were being incorrectly sorted - Fix
Rename::Ord/PartialOrd/Eq/PartialEqimpls inCaseEnforcerthat compared only depth, violating theEq/Ordcontract; sorting is now done viasort_by_keywithReverseon depth - Reject
stdin (-)with--mode=list, consistent with the existing--mode=writerestriction; produces a structured error via the sameStdinCannotBeUsedWithListvariant
Refactoring
- Replace ad-hoc
eprintln!calls for early validation errors inGhafmt::runwith structuredErrorvariants rendered throughrender_error, so colour mode is respected and diagnostics are consistent - Extract
build_handlerandrender_erroraspub(crate)free functions; remove them from theCommandtrait entirely — callers use the free functions directly - Simplify
FormatterResult: replaceoriginal: Option<String>+ accessor method with a plainpub(crate) original: Stringfield - Change
Ghafmt::runsignature frommut selfto&mut self - Pre-compute static key-ordering
Vec<String>fields in transformer struct constructors instead of allocating on everyprocess()call; affected transformers:TopLevelSorter,JobSorter,StepSorter,ConcurrencySorter,StrategySorter,ContainerSorter,RunsOnSorter,EnvironmentSorter,WorkflowCallSorter - Change
sort_mapping_at_pathandclassify_renamessignatures to accept&[T]instead of&Vec<T> - Make the
WorkflowProcessortransformer pipeline injectable viaWorkflowProcessor::new(Vec<Box<dyn StructureTransformer>>)with aDefaultimpl that builds the standard pipeline; removes the need for a test-only constructor - Derive
CopyonStateenums inJobsBlankLinesandStepsBlankLines; remove now-redundant.clone()calls at match sites - Remove stale
#[allow(unused_assignments)]fromerrors.rs - Fix
--colourflag spelling in the CLI integration test (was--color, causing the test to pass vacuously against a clap parse error)
Tests
- Add unit tests for
source_windowandline_col_to_byte_offsetinerrors.rs - Add integration tests for
WorkflowProcessorerror recovery: failed transformer emits a warning, document is restored, and subsequent transformers still run - Add
list_mode_rejects_stdinCLI integration test - Add
pull_request_target_branches_ignore_sortedregression test inFilterSorter - Remove duplicate
multi_job_comments_between_stepstest case inStepsBlankLines(5fd7503)
Fix --check silent failure and multi-file error message
--check now prints a unified diff to stderr for each file that differs from its
formatted form, instead of silently exiting 1. The no-flag multi-file error message
now mentions --check and --list alongside --write. (4d2689b)
Fix Docker build failure due to missing musl headers
The Docker build was failing because bindgen (used by fyaml-sys) invoked clang with the
default glibc include paths, causing a fatal error when it couldn’t find
bits/libc-header-start.h.
Fixed by setting BINDGEN_EXTRA_CLANG_ARGS=--sysroot=/usr/local/musl/${RUST_MUSL_CROSS_TARGET}
in the cargo build step, pointing clang at the musl sysroot already bundled in the
rust-musl-cross image. This works for both linux/amd64 and linux/arm64 targets. (f569f76)
Fix folded scalars emitting extra blank lines
When a string value used a >/>-/>+ folded scalar an extra trailing blank line
would be emitted after it.
This was due to an underlying bug in the YAML parsing library (libfyaml) which
is now resolved. (062854a)
Fix GitHub licence detection
Replaced the LICENSE symlink with a real file (renamed from LICENSE-MIT) so that
GitHub’s licence detector correctly identifies the repository as MIT without reporting
duplicates. LICENSE-APACHE is retained for dual-licence completeness. (fffec1f)
Fix case enforcement clobbering YAML literal block scalars
When a string value used a | block scalar (e.g. multi-line ${{ }} expressions),
set_yaml_at would silently fold newlines to spaces instead of failing, causing the
updated value to be written as a single-line string. The enforcer now detects literal
block style up front and formats the replacement value as a proper | block scalar
before writing. (1d769c1)
Fix literal keep (|+) scalars producing double blank lines
When a step used a |+ (literal keep) block scalar, formatting would insert an extra
blank line after it on every pass, breaking idempotency.
The underlying libfyaml bug that caused |+ to emit a spurious extra trailing newline
was fixed upstream (PR #268). However the blank-line insertion logic in ghafmt was not
accounting for the blank line already present in the event stream from the keep-chomping
semantics, so it would add another on top.
The fix detects when two consecutive line-break events already exist at the insertion point and skips insertion in that case. (6f07276)
Cleaner nightly version strings
Nightly builds now report nightly.YYYYMMDD.SHORTSHA from --version instead of the previous
0.0.0-nightly.YYYYMMDD.SHORTSHA. Nightly artifact filenames follow the same format. (088fed0)
Security audit remediation
Address findings from an internal security audit of the binary, CI/CD pipeline, supply chain, and repository hygiene.
Supply chain:
- Pin
fyamlandfyaml-syspatch dependencies to exact commit SHAs (rev =) instead of a mutable branch, preventing silent updates oncargo update - Pin
similarto=2.7.0for consistency with all other exact-pinned deps
CI/CD:
- Replace the unauthenticated
curl | bashactionlint install with a pinned release download and SHA-256 checksum verification (version sourced from the release checksums file, so Renovate bumps only the version) - Remove dead Windows packaging code from
_build_artifacts.yml(Windows is not in the build matrix) - Generate
.sha256files alongside release tarballs and verify them inaction.ymlbefore extracting the downloaded binary - Digest-pin the Dockerfile base images (
amd64-muslandarm64-musl) using per-platform digest pinning via named multi-stageFROMaliases - Pin the fallback
yqdownload inset-cargo-versionto a specific release with checksum verification (SHA-256 position derived fromchecksums_hashes_order)
Binary hardening:
- Disable symlink following in
--writemode to prevent path-traversal writes via symlinks pointing outside the target directory - Atomic file writes via
tempfile::NamedTempFile+ rename, preventing partial files on interrupted writes - Cap stdin consumption at 10 MB, returning an error if the limit is exceeded
- Source code embedded in parse error diagnostics is now limited to ±5 lines around the error, preventing secrets elsewhere in the file from leaking to stderr
- Replace
.unwrap()with.unwrap_or_default()in YAML sort closures to avoid panics on non-scalar keys
Repository hygiene:
- Add
SECURITY.mdwith a private vulnerability disclosure path - Add explanatory comments to suppressed advisory and
multiple-versions = "allow"indeny.toml(29d5880)
Use install-action for cargo-deny in CI
Switch the deny CI job from EmbarkStudios/cargo-deny-action to
taiki-e/install-action, fetching a pre-compiled binary instead of
compiling from source on every run. (deac02e)
Project Infrastructure
- Add rust-toolchain.toml as single source of truth for Rust version (28b49be)
- Migrate Renovate config (96edec2)
Add CI infrastructure
Sets up the full CI pipeline and release tooling for the project:
- Code checks (
code_checks.yml): nightlyrustfmt,clippy,cargo test(withactionlintinstalled), andcargo denyfor security advisories - Workflow checks (
workflow_checks.yml):actionlintvia Docker image andghafmt --checkto keep workflow files formatted - Dependency updates (
renovate.json): Renovate configured to update GitHub Actions SHA pins, Cargo dependencies, and the pinned Rust toolchain version - Changeset tracking (
knope.toml):knope create-changesetworkflow for documenting changes per PR
Also fixes several pre-existing issues uncovered by the new checks: invalid GPL-3 SPDX identifier, pending tests not
being skipped in CI, failing actionlint tests caused by unresolvable reusable workflow references, and clippy
warnings in integration test helpers. (8f871f7)
Milestone automation in release workflow
Releases now require a GitHub milestone titled v{version} (e.g. v0.1.0)
to exist before the release workflow will proceed. Once the release is
published the milestone is closed automatically. (664180a)
Nightly binary builds
A nightly GitHub pre-release is published each night at
https://github.com/jonathanrainer/ghafmt/releases/tag/nightly with
pre-built binaries for all four supported platforms. Nightly binaries
report their build date and commit in --version output. (247bd28)
Manual trigger for nightly build workflow
The nightly build workflow can now be triggered manually via
workflow_dispatch in addition to the scheduled nightly run. (882b45c)
Fix prepare-release PR title format
The PR and commit created by prepare-release now use
chore: Prepare To Release v$version instead of
chore: prepare release $version. (cfe4dd9)
Fix Renovate changeset deduplication
Renovate changesets are now named renovate_pr_<number>.md instead of deriving a slug from
the branch name. The old slug-based approach silently skipped changeset creation when a file
for the same package already existed from a previous PR, meaning repeated dependency bumps
went undocumented. Using the PR number guarantees a unique file per PR. (96edec2)
Pass secrets through to the build artifacts reusable workflow
Added secrets: inherit to both callers of _build_artifacts.yml (nightly_build.yml and
release.yml) so that repository secrets are available inside the reusable workflow. (ca26e4b)
cargo-edit is prohibited from downgrading a version, therefore we have to us yq in TOML
mode so we can support our nightly builds (4092bab)
Harden repository security ahead of going public
Added CODEOWNERS to auto-request review on all PRs. Tightened workflow permissions in
release.yml and prepare_release.yml by moving broad top-level permissions blocks down
to per-job level, so each job holds only the minimum permissions it needs. (796104d)
Pin action example to SHA and automate updates on release
The GitHub Action usage example in the README now pins to a full commit SHA rather than a floating version tag, following the recommended security practice for GitHub Actions:
- uses: jonathanrainer/ghafmt@1cc43c68845e56ea46b7a9c6017e024283081648
The prepare-release workflow in knope.toml now automatically updates this pin to the correct
SHA and version comment (e.g. # v0.3.0) as part of the release prep commit, so the docs stay
accurate across releases.
Also suppresses a Dockerfile linter warning about --platform=linux/amd64, with an explanatory
comment clarifying the intent. (4237b9d)
Auto-generate changesets for Renovate PRs
Adds a renovate_changeset.yml workflow that automatically commits a .changeset/*.md file
to every Renovate PR, preventing CI failures caused by missing changesets. The workflow
conditions on the PR author (github.event.pull_request.user.login) rather than the event
actor so that manually closing and reopening a PR still triggers changeset generation.
Also adds a Dependencies changelog section to knope.toml so dependency updates appear
under their own heading in the CHANGELOG. (8f871f7)
Set Renovate to update dependencies in real time
Removes the default weekly schedule so Renovate raises PRs as soon as updates are detected. (8f871f7)
Dependencies
- Update Rust crate tracing-subscriber to v0.3.23 (96edec2)
- Update taiki-e/install-action action to v2.68.33 (originally merged in #28) (96edec2)
- Update Rust crate assert_cmd to v2.2.0 (96edec2)
- Update Rust crate clap to v4.6.0 (96edec2)
- Update actions/checkout action to v6 (96edec2)
- chore(deps): update dependency rust-lang/rust to v1.94.0 (96edec2)
- chore(deps): update taiki-e/install-action action to v2.68.34 (96edec2)
- chore(deps): update taiki-e/install-action action to v2.68.35 (96edec2)
- chore(deps): update taiki-e/install-action action to v2.69.1 (ecb3daa)
- chore(deps): update taiki-e/install-action action to v2.69.2 (e8bc0cc)
- chore(deps): update taiki-e/install-action action to v2.69.5 (78ffcf8)
- chore(deps): update taiki-e/install-action action to v2.69.6 (9cc85d9)
Docker Image
docker pull ghcr.io/jonathanrainer/ghafmt:0.1.0
docker pull ghcr.io/jonathanrainer/ghafmt:latest