noya-cli
noyafmt and noyavalidate —
the YAML formatter and validator built on the noyalib
library.
Contents
- Install — every channel mapped
- Requirements — toolchain floor, platforms, the core pin
- Quick Start — common workflows
noyafmt— formatter referencenoyavalidate— validator + autofix reference- GitHub Action and pre-commit
- Exit codes — for shell pipelines and CI gates
- Examples — runnable demo scripts
- Shell completions and man pages
- Verification — cosign + SLSA cookbook
- When not to use these tools
- Documentation
- License
Install
| Channel | Command |
|---|---|
| Pre-built binaries | download for 8 targets (Linux gnu/musl, macOS, Windows on x86_64/aarch64) from the latest release — each with manpages, completions, SHA-256, and SLSA provenance |
| Linux packages | .deb and .rpm on the latest release |
| Homebrew (macOS/Linux) | brew install sebastienrousseau/tap/noya-cli |
| Scoop (Windows) | scoop bucket add sebastienrousseau https://github.com/sebastienrousseau/scoop-bucket then scoop install noya-cli |
| AUR (Arch) | noyalib-bin, e.g. yay -S noyalib-bin |
| Cargo (crates.io) | cargo install noya-cli --locked |
| Cargo (from source) | cargo install --locked --path . |
| GNU Make (binaries + manpages + completions) | make install — honors PREFIX (default /usr/local) and DESTDIR; make uninstall reverses it |
| Container (GHCR) | docker run --rm -v "$(pwd):/work" -w /work ghcr.io/sebastienrousseau/noya-cli:latest config.yaml validates; add --entrypoint noyafmt to format. Multi-arch, SLSA-attested, cosign-signed; first published with v0.0.41. |
Formatter only, if you do not need noyavalidate
cargo install noya-cli --locked --no-default-features --features noyafmt
The default install includes noyavalidate, whose schema errors are
rendered by miette with source
excerpts and carets — worth the weight for a validator, since pointing at
the offending line is the job. It does mean the default pulls 130
crates against 32 for the formatter alone, most of the difference
being miette’s fancy renderer and its backtrace stack.
If you only ever run noyafmt, the command above is a smaller supply
chain and a faster build. Nothing is degraded for anyone who wants the
diagnostics — it is a choice, not a default.
Releases ship the crate archive with a CycloneDX SBOM and sigstore bundles, plus the pre-built binary archives and Linux packages above, each with SHA-256 and a SLSA build-provenance attestation — see Verification.
MSRV: Rust 1.89.0, the floor shared across the noyalib family and the
maintainer’s other Rust crates (noyalib ADR-0014). The
toolchain-driven floor is lower: criterion 0.8 (the benchmark dev-dependency) declares
rust-version = 1.86, so cargo check --all-targets and the
bench suite fail on 1.85 (criterion@0.8.2 requires rustc 1.86),
though cargo check --lib still builds. clap_builder 4.6 in the
CLI dep tree is edition-2024. We publish the number we verify.
Requirements
- Rust 1.89.0 or newer to build from source:
rust-versionin the manifest, enforced by themsrv-coreCI job on every push. - Any tier-1 platform. CI runs the tests on Linux, macOS, and Windows with the stable, beta, and nightly toolchains; stable is the gate, beta and nightly are early warning.
- The matching core. This crate pins
noyalibat the identical=0.0.Xand releases in lockstep with it; Cargo resolves that pin for you. - Nothing at runtime: the binaries are static where the platform allows (musl on Linux) and link only the C runtime elsewhere.
Quick Start
# Format a file in-place; comments + indentation preserved.
noyafmt --write config.yaml
# CI gate — exits 1 if any file would change.
noyafmt --check ci/*.yaml
# Validate syntax + JSON Schema 2020-12.
noyavalidate --schema schema.yaml deploy.yaml
# Validate + auto-fix obvious type slips (port: "8080" → 8080).
noyavalidate --schema schema.yaml --fix deploy.yaml
noyafmt
YAML formatter mirroring the rustfmt / prettier ergonomics:
noyafmt config.yaml # print formatted source to stdout (default)
noyafmt --write config.yaml # rewrite in place
noyafmt --check ci/*.yaml # CI gate
noyafmt --indent 4 config.yaml # override default 2-space indent
cat foo.yaml | noyafmt --stdin # editor pipe (Vim, Emacs, …)
git ls-files '*.yaml' | xargs noyafmt --check
The formatter runs through noyalib’s lossless CST: comments, anchor positions, and document structure are preserved byte-for-byte; only whitespace and quoting are normalised.
| Flag | Effect |
|---|---|
--check | Verify each FILE is formatted; print files that need formatting; exit 1 if any do. Non-destructive. |
--write | Rewrite each FILE in place. Default is to print to stdout. Mutually exclusive with --check. |
--stdin | Read from stdin, write to stdout. Mutually exclusive with FILE arguments. |
--indent N | Indentation width in spaces, 1 to 16 (default: 2). |
noyavalidate
YAML syntax checker with optional JSON Schema 2020-12 enforcement and schema-driven autofix.
noyavalidate manifest.yaml # syntax only
noyavalidate --schema schema.yaml deploy.yaml # + schema check
noyavalidate --schema schema.yaml --fix deploy.yaml # + autofix
cat manifest.yaml | noyavalidate # stdin
The autofix engine
(coerce_to_schema)
rewrites string-shaped scalars into the schema’s expected type
when the parse succeeds. Loops until convergence; unparseable
inputs (port: "abc" against type: integer) are left in place
so a follow-up validate_against_schema call surfaces the
residue.
Diagnostics use miette for
rustc-style source pointers:
× schema violation: "8080" is not of type "integer"
╭─[deploy.yaml:3:7]
2 │ replicas: 3
3 │ port: "8080"
· ─┬───
· ╰── here
4 │ host: api
╰────
help: pass --fix to coerce string-shaped scalars to the
schema's declared type.
| Flag | Effect |
|---|---|
-s, --schema PATH | Validate each document against JSON Schema 2020-12 at PATH (the schema may itself be YAML or JSON). |
--fix | Rewrite FILE in place via the CST formatter (lossless: byte-faithful for everything except normalised whitespace / line endings). With stdin input, the formatted bytes go to stdout. |
-q, --quiet | Suppress success output. |
GitHub Action and pre-commit
One step in CI, no toolchain on the runner:
- uses: sebastienrousseau/noya-cli@v0.0.58
with:
paths: config/ deploy.yaml
schema: schema.json # optional
The action downloads the release binaries for the runner’s platform and
verifies their build provenance attestation with gh attestation verify
(built by this repository’s release workflow at the requested tag); where
the gh CLI or a token is missing it checks only the SHA-256 published in
the same release and says so with a warning. It then runs
noyafmt --check and noyavalidate (with the schema when given) over the
YAML under paths; directories are searched for *.yaml and *.yml.
Either failure fails the step, and tool output cannot issue workflow
commands. version must be X.Y.Z.
The same checks as hosted pre-commit hooks:
repos:
- repo: https://github.com/sebastienrousseau/noya-cli
rev: v0.0.58
hooks:
- id: noyafmt-check
- id: noyavalidate
noyafmt (runs noyafmt --write, so the hook fails when it reformatted a
file), noyafmt-check and noyavalidate (checks every staged file) are
the three hook ids; pass --schema schema.json through args to validate.
pre-commit builds the hooks with cargo install without --locked, so
dependencies resolve to their newest compatible versions rather than this
repository’s Cargo.lock. For a locked build, run
cargo install noya-cli --locked and point a repo: local hook with
language: system at the installed binaries.
Exit codes
| Code | noyafmt | noyavalidate |
|---|---|---|
| 0 | success (or no changes if --check) | all valid |
| 1 | parse / I/O error, or --check found unformatted file(s) | parse error or schema violation |
| 2 | invalid usage (bad arg combination) | invalid usage |
| 3 | — | I/O error (read / write) |
Examples
End-to-end runnable demos under
examples/:
| Script | What it shows |
|---|---|
format-precommit.sh | Drop-in git pre-commit hook gating commits on noyafmt --check. |
validate-k8s.sh | CI step that runs noyavalidate --schema over a directory of Kubernetes manifests. |
fix-quoted-numbers.sh | Walkthrough of the --fix autofix flow: quoted scalar → schema-typed integer, with the surrounding comment preserved. |
chmod +x crates/noya-cli/examples/*.sh
crates/noya-cli/examples/fix-quoted-numbers.sh
Shell completions and man pages
Tarball releases ship pre-built completions for bash, fish, zsh,
and PowerShell, plus roff man pages. Distro packages drop them
into the standard system locations
(/usr/share/bash-completion/completions/,
/usr/share/man/man1/, …).
make install places all of them in the standard locations. If
installing via cargo install, the pre-built copies live in this
repository (complete/, docs/*.1) — or regenerate them from the
clap definitions:
git clone https://github.com/sebastienrousseau/noya-cli
cd noya-cli
make assets # regenerates complete/* and docs/*.1 via build.rs
CI enforces that the committed copies are bit-identical to what the
clap definitions generate (make check-assets), so they cannot
drift from --help.
Verification
Every release artefact (crate archive, SBOM) ships with a cosign keyless signature and a SLSA build-provenance attestation. Verify before trusting a download:
COSIGN_EXPERIMENTAL=1 cosign verify-blob \
--certificate-identity-regexp '^https://github\.com/sebastienrousseau/noya-cli/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--bundle <artefact>.bundle \
<artefact>
gh attestation verify <artefact> --repo sebastienrousseau/noya-cli --signer-workflow sebastienrousseau/noya-cli/.github/workflows/release.yml
Full cookbook including the offline / FIPS-bound flow:
pkg/VERIFY.md.
When not to use these tools
- You need to format YAML faster than human-perceivable
latency.
noyafmtruns end-to-end on a 1 MiB document in ~10 ms; for<100 KBdocuments that’s already invisible. But for a streaming editor pipeline that wants per-keystroke formatting, the LSP server (noyalib-lsp) issues incrementalTextEdit[]s instead. - You need YAML 1.1-only behaviour, top to bottom.
noyavalidatefollows YAML 1.2; thelegacy_booleansopt-in is exposed at the library level but not yet plumbed through the CLI. - You need to embed the formatter or validator in your own
Rust binary. Use the
noyaliblibrary directly — every CLI feature flows through public library APIs (cst::format_with_config,validate_against_schema,coerce_to_schema).
Documentation
The four entry points, identical across every repo in the family:
-
User Manual — this crate’s rendered book: its guides, architecture, and release notes; the family manual for the core library is at https://sebastienrousseau.github.io/noyalib/manual/
-
API reference — rustdoc on docs.rs
-
Developer docs — this repo’s dev entry point, pointing at the family guide
-
Ecosystem map — the six crates, the lockstep model, the scorecard
-
Engineering policies (MSRV, SemVer, security, performance, concurrency, platform support, feature flags):
doc/POLICIES.md -
Security policy:
SECURITY.md -
CLI flag reference:
docs/cli-reference.md -
Recipes (CI gates, pre-commit, editor integration):
docs/recipes.md -
Workspace README: https://github.com/sebastienrousseau/noyalib#readme
-
Per-channel install + verify:
pkg/VERIFY.md -
Library API the binaries call into: https://docs.rs/noyalib
Conformance
Every push runs the official yaml-test-suite
through noyavalidate’s exit code, from the same vendored suite and the same
core commit as the noyalib core: 406 of 406 (valid cases exit 0, invalid
cases exit non-zero). A two-document configuration that uses most of YAML at
once (tests/fixtures/ultra-complex/) validates, and noyafmt’s output
parses to exactly the same JSON. Details and the family table:
noyalib.com/conformance.
License
Dual-licensed under Apache 2.0 or MIT, at your option.