Skip to content

Commit 5e05170

Browse files
authored
Merge pull request #204 from acgetchell/fix/203-uv-version-guard
fix(tooling): enforce pinned uv in local recipe guards
2 parents 6a3c762 + 938aff6 commit 5e05170

2 files changed

Lines changed: 81 additions & 9 deletions

File tree

justfile

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@ uv_version := "0.12.1"
3232
zizmor_version := "1.29.0"
3333

3434
# Internal helpers: ensure external tooling is installed
35-
_ensure-actionlint:
35+
_ensure-actionlint: _ensure-uv
3636
#!/usr/bin/env bash
3737
set -euo pipefail
38-
command -v uv >/dev/null || { echo "❌ 'uv' not found. Install with the official installer: https://docs.astral.sh/uv/getting-started/installation/"; exit 1; }
3938
uv run --locked actionlint -version >/dev/null
4039
4140
_ensure-cargo-llvm-cov:
@@ -121,16 +120,14 @@ _ensure-rumdl:
121120
exit 1
122121
fi
123122
124-
_ensure-shellcheck:
123+
_ensure-shellcheck: _ensure-uv
125124
#!/usr/bin/env bash
126125
set -euo pipefail
127-
command -v uv >/dev/null || { echo "❌ 'uv' not found. See 'just setup' or https://docs.astral.sh/uv/"; exit 1; }
128126
uv run --locked shellcheck --version >/dev/null
129127
130-
_ensure-shfmt:
128+
_ensure-shfmt: _ensure-uv
131129
#!/usr/bin/env bash
132130
set -euo pipefail
133-
command -v uv >/dev/null || { echo "❌ 'uv' not found. See 'just setup' or https://docs.astral.sh/uv/"; exit 1; }
134131
uv run --locked shfmt --version >/dev/null
135132
136133
_ensure-taplo:
@@ -163,12 +160,17 @@ _ensure-typos:
163160
_ensure-uv:
164161
#!/usr/bin/env bash
165162
set -euo pipefail
166-
command -v uv >/dev/null || { echo "❌ 'uv' not found. See 'just setup' or https://github.com/astral-sh/uv"; exit 1; }
163+
resolved="$(command -v uv 2>/dev/null || true)"
164+
actual="$(uv --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)"
165+
if [[ "$actual" != "{{ uv_version }}" ]]; then
166+
echo "❌ 'uv' resolves to '${resolved:-missing}' at version '${actual:-missing}', expected '{{ uv_version }}'." >&2
167+
echo " Install uv {{ uv_version }} and re-run: just setup-tools" >&2
168+
exit 1
169+
fi
167170
168-
_ensure-yamllint:
171+
_ensure-yamllint: _ensure-uv
169172
#!/usr/bin/env bash
170173
set -euo pipefail
171-
command -v uv >/dev/null || { echo "❌ 'uv' not found. See 'just setup' or https://docs.astral.sh/uv/"; exit 1; }
172174
uv run --locked yamllint --version >/dev/null
173175
174176
_ensure-zizmor:
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Regression tests for the Just recipe surface."""
2+
3+
import json
4+
import os
5+
import shutil
6+
import stat
7+
import subprocess
8+
from pathlib import Path
9+
from typing import Any
10+
11+
REPO_ROOT = Path(__file__).resolve().parents[2]
12+
13+
14+
def run_just(
15+
*args: str,
16+
check: bool = True,
17+
env: dict[str, str] | None = None,
18+
) -> subprocess.CompletedProcess[str]:
19+
"""Run the repository's installed Just executable without a shell."""
20+
executable = shutil.which("just")
21+
assert executable is not None
22+
return subprocess.run( # noqa: S603 - executable is resolved; arguments are fixed by tests.
23+
[executable, *args],
24+
cwd=REPO_ROOT,
25+
check=check,
26+
capture_output=True,
27+
encoding="utf-8",
28+
env=env,
29+
)
30+
31+
32+
def just_recipes() -> dict[str, dict[str, Any]]:
33+
"""Return parsed recipe metadata from the pinned Just executable."""
34+
result = run_just("--dump", "--dump-format", "json")
35+
recipes = json.loads(result.stdout)["recipes"]
36+
assert isinstance(recipes, dict)
37+
return recipes
38+
39+
40+
def test_uv_backed_helpers_reuse_pinned_guard() -> None:
41+
"""Local uv consumers should share one exact-version implementation."""
42+
recipes = just_recipes()
43+
ensure_uv_body = json.dumps(recipes["_ensure-uv"]["body"])
44+
setup_tools_body = json.dumps(recipes["setup-tools"]["body"])
45+
46+
assert "uv --version" in ensure_uv_body
47+
assert "uv_version" in ensure_uv_body
48+
assert "verify_tool_version uv" in setup_tools_body
49+
for name in ("_ensure-actionlint", "_ensure-shellcheck", "_ensure-shfmt", "_ensure-yamllint"):
50+
dependencies = {dependency["recipe"] for dependency in recipes[name]["dependencies"]}
51+
assert "_ensure-uv" in dependencies, name
52+
53+
54+
def test_uv_guard_reports_expected_and_actual_versions(tmp_path: Path) -> None:
55+
"""A mismatched uv executable should fail with actionable version details."""
56+
fake_bin = tmp_path / "bin"
57+
fake_bin.mkdir()
58+
fake_uv = fake_bin / "uv"
59+
fake_uv.write_text("#!/bin/sh\nprintf '%s\\n' 'uv 9.9.9'\n", encoding="utf-8")
60+
fake_uv.chmod(fake_uv.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
61+
62+
environment = os.environ.copy()
63+
environment["CARGO_HOME"] = str(tmp_path / "cargo")
64+
environment["PATH"] = f"{fake_bin}{os.pathsep}{environment['PATH']}"
65+
66+
expected = run_just("--evaluate", "uv_version").stdout.strip()
67+
result = run_just("_ensure-uv", check=False, env=environment)
68+
69+
assert result.returncode != 0
70+
assert f"version '9.9.9', expected '{expected}'" in result.stderr

0 commit comments

Comments
 (0)