build: pin local & CI tool versions in one place (mise / .tool-versions) #157

Closed
opened 2026-06-20 00:10:37 +00:00 by james · 0 comments
Owner

Problem

Local tooling and CI tooling can drift because they're version-pinned in
separate places — and some local installs aren't pinned at all.

Current state:

Tool CI pin Local pin
node node-version: "22" repeated in ~10 setup-node steps across .forgejo/workflows/pr.yml + secrets.yml; engines.node in package.json is >=20.9.0 none
gitleaks GITLEAKS_VERSION: 8.30.1 in .forgejo/workflows/secrets.yml none — lefthook only checks "is it on PATH"
actionlint ACTIONLINT_VERSION: 1.7.12 in .forgejo/workflows/pr.yml none — same shape as gitleaks

Two pain points:

  1. Local-vs-CI drift. A contributor on gitleaks 8.20 can pass a
    commit that CI (8.30.1) flags, or vice versa. "Works on my machine,
    red on CI" loop.
  2. Within-CI duplication. Node major "22" is duplicated across
    every setup-node step. Bumping in one place silently drifts the
    others; renovate would need a manager per occurrence.

Proposal

One source of truth for tool versions: a mise.toml (or
.tool-versions) at the repo root pinning node, gitleaks, and
actionlint. Both local hooks and CI activate the same versions from
that file.

  • Local: mise install after clone; lefthook hooks rely on
    mise-shimmed binaries. The "not installed" branch in lefthook.yml
    becomes "run mise install" instead of "brew install gitleaks".
  • CI: install mise once at the top of each job (or via a small
    reusable step), then mise install. The 10× node-version: "22"
    becomes one declaration.
  • Renovate: the mise manager picks up version bumps for all
    three tools from one file — same model as gomod / npm.

Out of scope

  • Switching CI to a devcontainer / nix flake — heavier setup tax, not
    warranted for three tools.
  • Replacing actions/setup-node entirely with mise-shimmed node —
    setup-node also handles npm cache restore; we keep it but read its
    version input from the mise.toml (or just have mise do it and drop
    setup-node — TBD during implementation, will be decided in the PR
    with the tradeoff explained inline).
  • Pinning everything else that happens to be installed (docker buildx,
    cosign, syft, etc.). Those run via SHA-pinned actions and are in
    scope of ADR-0009; not the same problem shape.

Acceptance

  • mise.toml (or .tool-versions) at repo root names exact versions
    for node, gitleaks, actionlint.
  • lefthook.yml hooks pass without any local tool installed beyond
    mise itself.
  • Every .forgejo/workflows/*.yml job that uses node / gitleaks /
    actionlint reads the version from the pin file, not from inline
    literals.
  • README "Requirements" updated: install mise, then mise install;
    the brew / Go-binary instructions for gitleaks + actionlint become
    fallback, not the default.
  • A short doc note (or ADR if the choice felt non-obvious) explains
    why this lives in mise.toml rather than package.json +
    inline-pinned binaries.
  • CI green; lefthook green on a fresh checkout after mise install.
## Problem Local tooling and CI tooling can drift because they're version-pinned in separate places — and some local installs aren't pinned at all. Current state: | Tool | CI pin | Local pin | | --- | --- | --- | | node | `node-version: "22"` repeated in ~10 `setup-node` steps across `.forgejo/workflows/pr.yml` + `secrets.yml`; `engines.node` in `package.json` is `>=20.9.0` | none | | gitleaks | `GITLEAKS_VERSION: 8.30.1` in `.forgejo/workflows/secrets.yml` | none — lefthook only checks "is it on PATH" | | actionlint | `ACTIONLINT_VERSION: 1.7.12` in `.forgejo/workflows/pr.yml` | none — same shape as gitleaks | Two pain points: 1. **Local-vs-CI drift.** A contributor on gitleaks 8.20 can pass a commit that CI (8.30.1) flags, or vice versa. "Works on my machine, red on CI" loop. 2. **Within-CI duplication.** Node major `"22"` is duplicated across every `setup-node` step. Bumping in one place silently drifts the others; renovate would need a manager per occurrence. ## Proposal One source of truth for tool versions: a `mise.toml` (or `.tool-versions`) at the repo root pinning `node`, `gitleaks`, and `actionlint`. Both local hooks and CI activate the same versions from that file. - **Local:** `mise install` after clone; lefthook hooks rely on `mise`-shimmed binaries. The "not installed" branch in `lefthook.yml` becomes "run `mise install`" instead of "brew install gitleaks". - **CI:** install mise once at the top of each job (or via a small reusable step), then `mise install`. The 10× `node-version: "22"` becomes one declaration. - **Renovate:** the `mise` manager picks up version bumps for all three tools from one file — same model as `gomod` / `npm`. ## Out of scope - Switching CI to a devcontainer / nix flake — heavier setup tax, not warranted for three tools. - Replacing `actions/setup-node` entirely with mise-shimmed node — setup-node also handles npm cache restore; we keep it but read its version input from the `mise.toml` (or just have mise do it and drop setup-node — TBD during implementation, will be decided in the PR with the tradeoff explained inline). - Pinning everything else that happens to be installed (docker buildx, cosign, syft, etc.). Those run via SHA-pinned actions and are in scope of ADR-0009; not the same problem shape. ## Acceptance - `mise.toml` (or `.tool-versions`) at repo root names exact versions for node, gitleaks, actionlint. - `lefthook.yml` hooks pass without any local tool installed beyond `mise` itself. - Every `.forgejo/workflows/*.yml` job that uses node / gitleaks / actionlint reads the version from the pin file, not from inline literals. - README "Requirements" updated: install `mise`, then `mise install`; the brew / Go-binary instructions for gitleaks + actionlint become fallback, not the default. - A short doc note (or ADR if the choice felt non-obvious) explains why this lives in `mise.toml` rather than `package.json` + inline-pinned binaries. - CI green; lefthook green on a fresh checkout after `mise install`.
james closed this issue 2026-06-20 01:21:01 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#157
No description provided.