From 71133a808f0793f0d59b8856b07b02df53faad42 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 13:38:40 +0200 Subject: [PATCH] Judge peer dependencies by range, and check the packageManager pin Two npm-specific corrections found by running the gate over web_ui. **`peerDependencies` must not be exact-pinned.** The gate was reporting `react: ">=19"` as "not exact-pinned", and acting on that would have been actively harmful: a peer range declares what a CONSUMER may bring, so pinning it forces every consumer of @kuhyx/web-ui onto one exact React. Peers are now judged the way toolchain ranges are -- does the range still admit latest? -- and only reported when it does not. **`packageManager` is a real dependency.** corepack enforces it, so `pnpm@10.13.1` is an exact pin that nothing was checking. It is now read and compared, which immediately surfaced that both TS packages sit on pnpm 10.13.1 against 11.24.0. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK --- dep_freshness/evaluate.py | 11 +++++++++++ dep_freshness/models.py | 1 + dep_freshness/parsers/javascript.py | 29 ++++++++++++++++++++-------- dep_freshness/tests/test_evaluate.py | 19 ++++++++++++++++++ dep_freshness/tests/test_parsers.py | 18 +++++++++++++++++ 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/dep_freshness/evaluate.py b/dep_freshness/evaluate.py index baef4f5..ab9d715 100644 --- a/dep_freshness/evaluate.py +++ b/dep_freshness/evaluate.py @@ -64,6 +64,17 @@ def judge(dep: Dep, answer: Answer) -> Finding | None: if dep.ecosystem == TOOLCHAIN: return _toolchain(dep, latest) + if dep.peer: + # A peerDependency declares what a CONSUMER may bring, so exact-pinning + # it is actively wrong: it would force every consumer onto one version. + # The meaningful question is whether the range still admits latest. + if not satisfies(dep.constraint, latest): + return Finding( + dep, Severity.STALE, latest, + detail=f"the peer range excludes the current {latest}", + ) + return None + if dep.pinned is None: if dep.caret_ok: floor = lower_bound(dep.constraint) diff --git a/dep_freshness/models.py b/dep_freshness/models.py index a581453..15d20f9 100644 --- a/dep_freshness/models.py +++ b/dep_freshness/models.py @@ -37,6 +37,7 @@ class Dep: dev: bool = False caret_ok: bool = False # Q13 carve-out: a range is legal here override: bool = False + peer: bool = False # a compatibility range, not a build pin @dataclass(frozen=True) diff --git a/dep_freshness/parsers/javascript.py b/dep_freshness/parsers/javascript.py index d008756..240323d 100644 --- a/dep_freshness/parsers/javascript.py +++ b/dep_freshness/parsers/javascript.py @@ -16,13 +16,14 @@ from dep_freshness.models import Dep from dep_freshness.parsers._lines import index from dep_freshness.versions import exact_pin +# (section, is_dev, is_override, is_peer) _SECTIONS = ( - ("dependencies", False, False), - ("devDependencies", True, False), - ("optionalDependencies", False, False), - ("peerDependencies", False, False), - ("resolutions", False, True), - ("overrides", False, True), + ("dependencies", False, False, False), + ("devDependencies", True, False, False), + ("optionalDependencies", False, False, False), + ("peerDependencies", False, False, True), + ("resolutions", False, True, False), + ("overrides", False, True, False), ) _LOCAL_PREFIXES = ("file:", "link:", "workspace:", "git+", "github:", "npm:") @@ -49,14 +50,26 @@ def parse_package_json(path: Path) -> list[Dep]: line=lines.get("node", 0), pinned=exact_pin(node), caret_ok=True, )) - for section, is_dev, is_override in _SECTIONS: + # `packageManager: pnpm@10.13.1` is an exact pin of the package manager + # itself, which corepack enforces -- a real dependency, and the only one + # three of these repos omit entirely. + manager = data.get("packageManager") + if isinstance(manager, str) and "@" in manager: + name, _, version = manager.rpartition("@") + deps.append(Dep( + ecosystem=NPM, name=name, constraint=version, path=path, + line=lines.get("packageManager", 0), pinned=exact_pin(version), + dev=True, + )) + + for section, is_dev, is_override, is_peer in _SECTIONS: for name, spec in (data.get(section) or {}).items(): if not isinstance(spec, str) or spec.startswith(_LOCAL_PREFIXES): continue deps.append(Dep( ecosystem=NPM, name=str(name), constraint=spec, path=path, line=lines.get(str(name), 0), pinned=exact_pin(spec), - dev=is_dev, override=is_override, + dev=is_dev, override=is_override, peer=is_peer, )) return deps diff --git a/dep_freshness/tests/test_evaluate.py b/dep_freshness/tests/test_evaluate.py index acff5e9..1d2ef85 100644 --- a/dep_freshness/tests/test_evaluate.py +++ b/dep_freshness/tests/test_evaluate.py @@ -110,6 +110,25 @@ def test_a_pinned_toolchain_behind_latest_is_stale(): assert finding.severity is Severity.STALE +def test_a_peer_range_admitting_latest_is_not_a_violation(): + """Exact-pinning a peer dep would force every consumer onto one version.""" + assert judge( + dep(ecosystem="npm", name="react", constraint=">=19", pinned=None, + peer=True), + Answer("19.2.8"), + ) is None + + +def test_a_peer_range_excluding_latest_is_reported(): + finding = judge( + dep(ecosystem="npm", name="react", constraint=">=19 <19.2", pinned=None, + peer=True), + Answer("19.2.8"), + ) + assert finding.severity is Severity.STALE + assert "peer range excludes" in finding.detail + + def test_no_answer_at_all_is_unknown_not_stale(): finding = judge(dep(), Answer(None, unavailable=True)) assert finding.severity is Severity.UNKNOWN diff --git a/dep_freshness/tests/test_parsers.py b/dep_freshness/tests/test_parsers.py index 66a2724..f5e7c4b 100644 --- a/dep_freshness/tests/test_parsers.py +++ b/dep_freshness/tests/test_parsers.py @@ -110,6 +110,24 @@ lint = ["ruff==0.16.5"] assert deps["python"].constraint == ">=3.10" +def test_package_json_reads_the_package_manager_pin(tmp_path): + """corepack enforces `packageManager`, so it is a real dependency.""" + path = write(tmp_path, "package.json", '{"packageManager": "pnpm@10.13.1"}') + deps = {d.name: d for d in javascript.parse_package_json(path)} + assert deps["pnpm"].pinned == "10.13.1" + + +def test_a_package_manager_without_a_version_is_ignored(tmp_path): + path = write(tmp_path, "package.json", '{"packageManager": "pnpm"}') + assert javascript.parse_package_json(path) == [] + + +def test_peer_dependencies_are_marked_as_such(tmp_path): + path = write(tmp_path, "package.json", + '{"peerDependencies": {"react": ">=19"}}') + assert javascript.parse_package_json(path)[0].peer + + def test_package_json_reads_engines_and_overrides(tmp_path): path = write(tmp_path, "package.json", """\ {"engines": {"node": "24.20.0"},