lefthook.yml
Rama master · réplica de lectura del repositorio original.
# SPDX-License-Identifier: FSL-1.1-Apache-2.0
#
# Local Git hooks managed by lefthook (https://lefthook.dev).
# Install with `lefthook install` after `nix run nixpkgs#lefthook --`
# or `bun add -D lefthook && bun lefthook install`.
#
# `pre-commit` uses the `jobs` form (lefthook >= 1.10) rather than
# `commands`, because only a job can carry a `group` — and the
# docsgen/docs-check pair needs one. See the comment on that group.
#
# GLOB SHAPE, once, because two globs here got it wrong: in lefthook's
# matcher `**` does NOT match zero directories when a separator
# follows it, so `docs/**/*.md` silently skips `docs/index.md` and
# `{cue.mod/**}/*.cue` silently skips `cue.mod/module.cue`. Any glob
# that must cover a tree AND its top level spells both out —
# `dir/{*.ext,**/*.ext}`. A TRAILING `/**` (no separator after) is
# fine and does match direct children, which is why the `docsgen` and
# `docs/hooks/**` globs need no such treatment.
pre-commit:
parallel: true
jobs:
- name: cue
glob: "{cue.mod,schemas,examples}/{*.cue,**/*.cue}"
run: ./scripts/lint-cue.sh
- name: spdx
run: ./scripts/check-spdx-headers.sh
- name: cyrillic
run: ./scripts/check-no-cyrillic.sh
- name: crd-check
# `operator/crdgen/**` is in the glob for the same reason the
# docsgen job's glob covers `cli/docsgen/**`: the GENERATOR also
# changes what's committed, not just its CUE/Rust input — a
# crdgen-only edit (structural.rs, cue.rs, envelope.rs) can
# change the rendered CRD YAML with no schema or operator-core
# change at all. `.github/workflows/crd-check.yml` already lists
# it in `paths:`; this glob was the local half missing it.
glob: "{schemas/v1alpha1/*.cue,operator/operator-core/src/*.rs,operator/crdgen/**,operator/charts/apprafter-operator/templates/crd-*.yaml}"
run: ./scripts/crd-check.sh
# These two are ORDERED, and the rest of the hook is not.
#
# `docsgen` WRITES docs/reference/cli/; `docs-check` READS it (the
# strict mkdocs build, then the byte-compare). A commit touching
# both `cli/platform-cli/src/**` and a documentation page matches
# both globs — and that is the prescribed shape for a CLI change, which
# regenerates the reference in the same commit — so under the
# hook's `parallel: true` they would race: docs-check could read a
# half-written tree, or the pre-regeneration one, and fail or pass
# for the wrong reason.
#
# A `piped` group is lefthook's sequential construct: its jobs run
# in listed order and the group stops at the first failure (so a
# docsgen failure never reports itself as reference drift). The
# group as a whole still runs in parallel with cue/spdx/cyrillic/
# crd-check, which share no files with it. A job whose glob does
# not match is skipped without failing the group, so a docs-only
# commit still runs docs-check and a cli-only commit still runs
# docsgen.
#
# `priority` is NOT the mechanism here: it only orders jobs when
# the hook is sequential (`parallel: false` or `piped: true`), and
# this hook is parallel by design.
- name: docs
group:
piped: true
jobs:
# The CLI reference is generated from the clap tree, so a
# touched cli.rs (or anything else under platform-cli) is a
# docs change too. Regenerating and staging it here means a
# contributor never sees "the docs drifted" for a file they
# were never asked to edit — the gate in docs-check then
# only fires for a hand-edited generated page.
# `cli/docsgen/**` is in the glob for the same reason from
# the other side: the GENERATOR also changes the committed
# artefacts, not just its input. Editing `model.rs` alone
# (no clap change at all) can add or drop a projected node
# and change `commands.json` — proven by the root-binary
# node this hook's own glob was missing.
# `cli/Cargo.toml` is in the glob because commands.json
# embeds `cli_version` — a release bump is a reference
# change.
# The nix guard mirrors scripts/docs-check.sh: `just
# docsgen-generate` runs under `nix develop` for the pinned
# toolchain a byte-compare needs, and without nix a raw
# "command not found" says nothing about what to do next.
- name: docsgen
glob: "{cli/platform-cli/src/**,cli/docsgen/**,cli/Cargo.toml}"
run: |
command -v nix >/dev/null 2>&1 || {
echo "ERROR: regenerating the CLI reference needs nix — docsgen runs" >&2
echo "under 'nix develop' so local and CI render with one pinned" >&2
echo "toolchain. Install nix (flakes enabled) — see" >&2
echo "docs/contributing/setup.md — or skip the local hook with" >&2
echo "'git commit --no-verify'; the docs workflow still runs on the" >&2
echo "pull request." >&2
exit 2
}
just docsgen-generate && git add docs/reference/cli
# Wider than the pages themselves, because `docs-check` runs
# the 2.19c drift gate and the gate resolves what the docs
# CLAIM against what the tree SHIPS. Its two inputs are the
# clap tree (`cli/**`, same files as the `docsgen` job above)
# and the schemas (`schemas/v1alpha1` plus the CRDs generated
# from them). Renaming a field or a flag is therefore a
# documentation change even when no page is touched, and a
# commit that matched neither glob used to leave the finding
# for the next contributor's unrelated docs commit to hit.
# Mirrors the `paths:` filter in .github/workflows/docs.yml.
#
# The corpus is `docs/**` PLUS the root `README.md`, which is
# what `scan::in_scope` lists (`git ls-files -- docs
# README.md`) and one of the pages the census counts. It is
# named here because no `docs/` glob can reach it: without
# the entry, deleting or rewriting the README moved the
# census and fired nothing on that commit, leaving the
# finding to land on the NEXT contributor's unrelated docs
# pull request — the failure the wide globs below exist
# against.
#
# `docs/measurements/docs-health.json` is in the glob as a
# TRIGGER — editing the committed corpus census must re-run
# the gate that compares against it — and it is spelled out
# because the `.md` globs beside it do not cover a `.json`.
#
# It must NEVER be regenerated here, the way the `docsgen`
# job above regenerates docs/reference/cli and `git add`s it.
# That job is safe only because its TRIGGER (`cli/**`) and its
# ARTEFACT (`docs/reference/cli/**`) live in different trees,
# so a docs-only commit never rewrites it. This file's trigger
# IS `docs/**`: a `docsgen metrics && git add` step here would
# rewrite and stage the census on every documentation commit,
# so the committed numbers would always equal the current
# ones, every deletion would be recorded as intended, and the
# ratchet would report success on exactly the change it exists
# to catch. Re-recording is a deliberate act — run `docsgen
# metrics` by hand, in the same commit, and say why in the
# commit message.
- name: docs-check
glob: "{README.md,docs/{*.md,**/*.md},docs/measurements/docs-health.json,mkdocs.yml,docs/hooks/**,scripts/docs-check.sh,scripts/docs-artefacts-check.py,cli/platform-cli/src/**,cli/docsgen/**,cli/Cargo.toml,schemas/v1alpha1/*.cue,operator/charts/apprafter-operator/templates/crd-*.yaml}"
run: ./scripts/docs-check.sh
# THE VERSION GATES.
#
# These lived ONLY in `just lint`, and no workflow invokes `just lint` —
# so the one check that catches "operator code changed but appVersion did
# not" fired only when somebody remembered to type it. It did not fire for
# the commit that shipped the surge-free rollout fix: that change passed
# fmt, clippy, the full workspace test suite and the docs gate, and would
# still never have become an image, because `release-operator`'s two-axis
# gate (tag exists + images exist) SKIPS the rebuild when appVersion has
# not moved. Committed, green, and silently not shipped — the class of
# miss behind the 0.2.40/0.2.41 double-yank.
#
# Safe on a pre-commit hook for two MEASURED reasons. Each script is ~2s,
# and they are listed FLAT rather than inside a `group:` so the hook's own
# `parallel: true` applies to them — measured: the same three scripts take
# 6251ms one after another and 1999ms together, and a `group:` carrying
# `parallel: true` ran them sequentially anyway on lefthook 2.1.5. A group
# here would buy a nicer heading and cost 4 seconds on every commit that
# touches platform-stack. Second reason: each asks `git ls-remote` whether
# its tag is already PUBLISHED, and with no network that call fails. Outside
# CI the scripts then print a warning that the check did NOT run and exit 0
# (scripts/lib/published-tag.sh; they used to say "in flight" instead). An
# offline commit therefore LOSES the check rather than breaking on it — and
# loses it only until the pull request: since WI-373 the `version-guards`
# job in .github/workflows/lint.yml runs the same scripts on every pull
# request and push to master, and there an undecided guard FAILS. That job
# is also what catches the commits this hook never sees: `--no-verify`, a
# clone without `lefthook install`, a web edit, a merge commit.
#
# `version-coherence` gets no glob narrower than the union of the others:
# it cross-checks the platform-stack pins against BOTH chart appVersions
# and the cue-cmp version, so a file from any of those trees can break it.
- name: operator-version-bump
glob: "{operator/**,schemas/v1alpha1/*.cue}"
run: ./scripts/check-operator-version-bump.sh
- name: cli-version-bump
glob: "cli/**"
run: ./scripts/check-cli-version-bump.sh
- name: backup-runner-pin
glob: "{cli/apprafter-backup/**,cli/backup-core/**,cli/Cargo.toml,platform-stack/cue/platform.cue}"
run: ./scripts/check-backup-runner-pin.sh
- name: cue-cmp-drift
glob: "argocd-cue-cmp/**"
run: ./scripts/check-argocd-cue-cmp-drift.sh
- name: cue-cmp-mirror
glob: "{argocd-cue-cmp/plugin.yaml,platform-stack/cue/component_argocd.cue}"
run: ./scripts/check-cue-cmp-mirror.sh
- name: version-coherence
glob: "{operator/charts/**,platform-stack/**,argocd-cue-cmp/**,cli/Cargo.toml}"
run: ./scripts/check-version-coherence.sh
# The remaining `just lint` script gates. All offline, all a few seconds at
# most, all glob-scoped, so they cost nothing on a commit that cannot break
# them.
#
# What is deliberately NOT here: `just lint` also runs `cargo fmt`,
# `cargo clippy` and the docsgen property tests across two workspaces.
# Those are minutes, not seconds, and a pre-commit hook that costs minutes
# gets bypassed with `--no-verify`, which loses the cheap checks too. They
# stay in `just lint` and in the lint/test workflows, where a cold build is
# expected.
- name: heredoc-backticks
glob: "{scripts/**,e2e/**,.github/workflows/**,argocd-cue-cmp/**}"
run: ./scripts/check-heredoc-backticks.sh
# dump_diagnostics is what a failing walk leaves behind, and nothing runs
# it until a walk fails. See the header of the script.
- name: dump-diagnostics
glob: "{e2e/lib.sh,scripts/check-dump-diagnostics.sh,operator/operator-core/src/deadline.rs}"
run: ./scripts/check-dump-diagnostics.sh
# A workflow that does not parse fails as a run with ZERO JOBS, which
# `gh pr checks` does not list — every check can be green while a
# release workflow is broken. See the header of the script.
- name: workflows
glob: ".github/workflows/**"
run: ./scripts/check-workflows.sh
- name: crd-structural
glob: "operator/charts/apprafter-operator/templates/crd-*.yaml"
run: ./scripts/check-crd-structural.sh
- name: argocd-health-lua
glob: "platform-stack/**"
run: ./scripts/check-argocd-health-lua.sh
- name: component-enablement
glob: "platform-stack/**"
run: ./scripts/check-component-enablement.sh
- name: plan-checkboxes
glob: "plan.md"
run: ./scripts/check-plan-checkboxes.sh
# There is deliberately NO job here for `landing/cms/payload-types.ts`,
# the generated file the `payload-types drift` step in
# .github/workflows/lint.yml checks. Both shapes were built and their
# failures reproduced before being rejected:
#
# - Regenerate-and-stage, mirroring the `docsgen` job above, breaks under
# `git commit -- <paths>`, `--only` and `--amend -- <paths>`. Those run
# hooks against a THROWAWAY index (`.git/next-index-<pid>.lock`) that
# git discards afterwards, so the `git add` reaches the commit but never
# the real index: the artefact stays staged at its pre-hook bytes and
# the next plain commit silently reverts it, between two clean commits.
# `docsgen` shares this exposure; it is not a reason to add a second.
# - Either shape renders from the WORKING TREE while the commit is the
# INDEX. lefthook hides unstaged hunks only for PARTIALLY staged files,
# so a sibling global that is modified but wholly unstaged is baked into
# the artefact anyway — staged into a commit whose sources do not
# contain it, or reported as drift the commit does not have.
#
# CI is where index, tree and commit are one thing, so that is where the
# byte-compare runs. `scripts/check-payload-types.sh` carries the long
# version of this reasoning.
commit-msg:
commands:
conventional:
run: ./scripts/check-commit-msg.sh "{1}"