mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-28 09:55:39 -04:00
## Summary `pnpm update --recursive --lockfile-only <pkg>@<version>` crashed with `Invalid Version: <range>` when the catalog entry for `<pkg>` was a range (e.g. `^21.2.10`) and `catalogMode` was `strict` or `prefer`. This is the exact command Renovate's pnpm artifact updater runs; monorepos using `catalog:` with any range specifier were blocked from Renovate-driven lockfile updates. **Root cause:** in `installSome`, the catalog-match short-circuit guards `semver.eq(wantedDep.bareSpecifier, catalogDepSpecifier)` with `semver.validRange` on both sides. `validRange` returns truthy for ranges, but `semver.eq` constructs `new SemVer(...)` internally and throws on a range. **Fix:** use `semver.valid` instead of `semver.validRange` on both sides of the equality guard. Range specifiers now fall through to the existing mismatch handling (`CatalogVersionMismatchError` in `strict` mode, warn-and-use-direct in `prefer` mode) instead of crashing. Behavior for concrete-on-both-sides is unchanged. Closes #11570 ## Behavior after the fix This turns a crash into pnpm's normal catalog-mismatch handling; it does **not** make a strict-mode update succeed when the catalog is a range: - **`catalogMode: strict`** — rejects with `ERR_PNPM_CATALOG_VERSION_MISMATCH` (clean, actionable error instead of a stack trace). - **`catalogMode: prefer`** — warns and uses the direct version. - **concrete-vs-concrete** — unchanged (`semver.eq` still runs). ## pacquet parity The TypeScript fix patches a crash inside pnpm's `catalogMode` mismatch gate — a feature pacquet had not ported at all (`catalog-mode` was in the config parity test's `NOT_PORTED` list). Rather than just the one-liner, this PR ports that gate to pacquet so the two stacks match: - **config:** new `CatalogMode { Manual, Strict, Prefer }` enum (default `manual`), `Config.catalog_mode`, wired through `pnpm-workspace.yaml` (`catalogMode:`) and the env overlay; `catalog-mode` moved from `NOT_PORTED` to a mapped row in the `pnpm_default_parity` contract test. - **package-manager:** `check_catalog_mode` + `CatalogVersionMismatchError` (`ERR_PNPM_CATALOG_VERSION_MISMATCH`), invoked from `update` before the manifest is mutated. The comparison only treats both sides as equal when each parses as a concrete semver version, so a ranged catalog entry falls through to the mismatch path instead of reaching an exact-version comparison — the Rust analogue of the `semver.valid` guard above. The crash itself can't occur in pacquet (Rust's `node-semver` returns a `Result` rather than throwing); the port is the *feature* with the range-correct comparison built in, so pacquet behaves like fixed pnpm. **Not ported** (the surrounding pieces pacquet still lacks, so wiring them would diverge from pnpm rather than match it): the `add`-path cataloging that relies on `defaultCatalog` rewriting, and the `saveCatalogName` → `pnpm-workspace.yaml` auto-cataloging half. The gate is therefore wired into `update <pkg>@<version>` / `--latest` (the Renovate scenario), not `add`.