From f02484feba6d3bb35fa00be6e1d16de8e26e12ab Mon Sep 17 00:00:00 2001 From: xxxigm Date: Mon, 15 Jun 2026 22:20:30 +0700 Subject: [PATCH] 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. --- tests/test_assistant_ui_tap_compat.py | 141 ++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/test_assistant_ui_tap_compat.py diff --git a/tests/test_assistant_ui_tap_compat.py b/tests/test_assistant_ui_tap_compat.py new file mode 100644 index 00000000000..57c9e874819 --- /dev/null +++ b/tests/test_assistant_ui_tap_compat.py @@ -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("^~>= 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")