test(deps): guard @assistant-ui cluster on one tap version

Lockfile invariant that would have caught the desktop build break: the
single hoisted @assistant-ui/tap must satisfy every @assistant-ui/*
package's declared tap requirement (deps or non-optional peer). It is a
contract, not a snapshot -- no hardcoded versions -- so it stays green
across routine bumps but fails the moment the cluster splits its tap
requirement again.
This commit is contained in:
xxxigm 2026-06-15 22:20:30 +07:00 committed by ethernet
parent eae3836eb6
commit f02484feba

View file

@ -0,0 +1,141 @@
"""Invariant: the @assistant-ui dependency cluster agrees on one tap version.
The Hermes desktop app (``apps/desktop``) is built from source on every
install/update via ``scripts/install.ps1`` ``npm ci``/``npm install``
``tsc -b && vite build``. The ``@assistant-ui`` packages share an internal
reactivity lib, ``@assistant-ui/tap``, and they only interoperate when they
all resolve the *same* tap version:
* ``@assistant-ui/react@0.12.28`` and ``@assistant-ui/core`` pin
``@assistant-ui/tap@^0.5.x`` (which exports ``.`` and ``./react``).
* ``@assistant-ui/store@0.2.18`` bumped its tap peer to ``^0.9.0`` and started
importing ``@assistant-ui/tap/react-shim`` an entry point that only exists
in the tap ``0.9.x`` line.
Because ``react@0.12.28`` requests ``store@^0.2.9`` (a caret range), a fresh
install silently floated ``store`` up to ``0.2.18``, which then could not find
``./react-shim`` in the hoisted ``tap@0.5.x`` and crashed ``vite build`` with::
"./react-shim" is not exported ... from package @assistant-ui/tap
i.e. the opaque "apps/desktop build failed (exit 1)" every user hit when
updating. The fix pins ``@assistant-ui/store`` (via root ``overrides``) to the
last release that targets ``tap@^0.5.x``.
This is a *contract* test, not a snapshot: it does not assert specific version
numbers, only that whatever tap the lockfile hoists satisfies every
``@assistant-ui/*`` package's declared tap requirement. It fails if any future
bump reintroduces a split tap requirement across the cluster.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
TAP = "@assistant-ui/tap"
def _caret_satisfies(version: str, spec: str) -> bool:
"""Minimal npm semver check for the ranges this cluster actually uses.
Supports exact versions, ``^x.y.z`` (with correct 0.x semantics), and
``||`` unions. Pre-release tags are ignored (none are used here).
"""
def parse(v: str) -> tuple[int, int, int]:
core = v.lstrip("^~>=<v ").split("-")[0].split("+")[0]
parts = (core.split(".") + ["0", "0", "0"])[:3]
return tuple(int(p) for p in parts) # type: ignore[return-value]
ver = parse(version)
for clause in spec.split("||"):
clause = clause.strip()
if not clause:
continue
if clause.startswith("^"):
lo = parse(clause)
if ver < lo:
continue
major, minor, _ = lo
if major > 0:
hi = (major + 1, 0, 0)
elif minor > 0:
hi = (0, minor + 1, 0)
else:
hi = (0, 0, lo[2] + 1)
if ver < hi:
return True
elif clause[0].isdigit() or clause.startswith("v"):
if ver == parse(clause):
return True
return False
def _lock_packages() -> dict:
lock_path = REPO_ROOT / "package-lock.json"
if not lock_path.exists():
pytest.skip("package-lock.json not materialized in this CI shard")
with lock_path.open("r", encoding="utf-8") as fh:
return json.load(fh).get("packages", {})
def _hoisted_tap_version(packages: dict) -> str:
entry = packages.get(f"node_modules/{TAP}")
assert entry is not None, (
"package-lock.json has no hoisted node_modules/@assistant-ui/tap "
"entry — the @assistant-ui cluster should resolve a single shared "
"tap version."
)
return entry["version"]
def test_assistant_ui_cluster_agrees_on_one_tap() -> None:
"""Every @assistant-ui/* package's tap requirement must be satisfiable.
Encodes the contract that broke the desktop build: a single hoisted
@assistant-ui/tap must satisfy the tap range declared by react, core,
store, and any sibling otherwise the missing ``./react-shim`` export
(or a similar API split) breaks ``vite build``.
"""
packages = _lock_packages()
tap_version = _hoisted_tap_version(packages)
offenders: list[str] = []
for key, meta in packages.items():
name = key.rsplit("node_modules/", 1)[-1]
if not name.startswith("@assistant-ui/") or name == TAP:
continue
peer_meta = meta.get("peerDependenciesMeta", {}).get(TAP, {})
if peer_meta.get("optional"):
continue
spec = meta.get("dependencies", {}).get(TAP) or meta.get(
"peerDependencies", {}
).get(TAP)
if not spec:
continue
if not _caret_satisfies(tap_version, spec):
offenders.append(f"{name} requires {TAP}{spec!r}")
assert not offenders, (
f"Hoisted {TAP}@{tap_version} does not satisfy: "
+ "; ".join(offenders)
+ ". The @assistant-ui cluster has split tap requirements — pin the "
"offending package (e.g. via root package.json `overrides`) so the "
"whole cluster shares one tap line. See this test's module docstring."
)
def test_caret_satisfies_helper() -> None:
"""Guard the tiny semver helper the invariant relies on."""
assert _caret_satisfies("0.5.14", "^0.5.10")
assert _caret_satisfies("0.5.14", "^0.5.14")
assert not _caret_satisfies("0.5.14", "^0.9.0")
assert not _caret_satisfies("0.5.14", "^0.6.0")
assert _caret_satisfies("1.2.5", "^1.2.0")
assert not _caret_satisfies("2.0.0", "^1.2.0")
assert _caret_satisfies("0.5.14", "^0.5.0 || ^0.9.0")