pnpm remove already skips resolution when the lockfile proves the outcome; its twin did not. `pnpm add <pkg>@<version>` re-resolved the whole graph even when the requested version was one the lockfile already held — promoting a transitive dependency to a direct one, or adding to a second workspace package what a first one already depends on. The rule is the one pnpm/pnpm#13779 established: resolution reuses the already-locked version it would dedupe onto rather than fetching a higher one, because preferredVersions biases it toward what the graph already holds. The importers handler now records a brand-new importer edge at that version, under the group the manifest declares, alongside the whole-new-project entry pnpm/pnpm#13803 added. It reuses that change's guards: a range several locked versions satisfy under `resolutionMode: time-based` or `lowest-direct` keeps the resolver, since those pick the low end and only for a run that leaves the manifest alone, and so does a plain range on a workspace project's name, which may resolve to a link instead. A lockfile carrying `time` keeps it too: `time` records a publish date per direct dependency and is only pruned on save, never extended, so a package promoted into that position would be missing the one a resolution records. `installSome` cannot edit the manifests in place the way `uninstallSome` does — the resolution path reads them to tell a new dependency from a re-added one, and reads them again to pick the range style it saves — so the edit is staged on copies, handed to the fast update and its freshness gates, and the context takes it only once the rewrite commits. The range a selector saves is computed with the resolver's own rule, extracted as `calcVersionRange` in `@pnpm/pkg-manifest.utils` alongside the `inferRangeSpecStyle` it needs. pacquet needed only the gate: `add` already pins the manifest before the install runs, so `may_fast_update_lockfile` admits `InstallSome` and `add` stops forcing `preferFrozenLockfile` off. `update` is also `InstallSome` and keeps its explicit opt-out, so it is unaffected. Its manifest pin still consults the registry packument; only the install-side resolution is skipped. The narrow cut is a plain registry specifier. A dist tag, an alias, a `workspace:`/`catalog:`/git/tarball specifier, `--save-peer`, a `catalogMode` other than `manual`, an overridden package, and a version nothing locked satisfies all keep the resolver. Closes pnpm/pnpm#13798.
919 B
919 B
@pnpm/pkg-manifest.utils, @pnpm/installing.deps-installer, @pnpm/resolving.npm-resolver, pacquet, pnpm
| @pnpm/pkg-manifest.utils | @pnpm/installing.deps-installer | @pnpm/resolving.npm-resolver | pacquet | pnpm |
|---|---|---|---|---|
| minor | patch | patch | patch | patch |
pnpm add no longer re-resolves the dependency graph when pnpm-lock.yaml already holds a version satisfying the request — promoting a transitive dependency to a direct one, or adding to a second workspace package what a first one already depends on, now only saves the dependency in package.json and records its importer entry. A satisfying locked version is necessary but not sufficient: the install still falls back to a full resolution for a dist tag, an alias, a workspace:/catalog:/git/tarball specifier, --save-peer, an overridden package, a catalogMode other than manual, and — under resolutionMode: time-based or lowest-direct, which resolve a direct dependency to the low end of its range — a range several locked versions satisfy.