mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
This commit is contained in:
parent
81d2493476
commit
71133a808f
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user