resolution-mode trade-off: time-based reproducibility versus highest currency for repeatable CI builds
0 reputation · 02 Aug 2021, 06:17 UTC
0 reputation · 02 Aug 2021, 06:17 UTC
Establish a repeatable development environment where pnpm install produces an identical pnpm-lock.yaml on every machine and in CI, regardless of when the lockfile is regenerated or which pnpm major version is used.
The resolution-mode setting controls how version ranges are resolved when the lockfile is updated. time-based selects the newest version that existed at the lockfile's recorded time reference, aiming to keep a regenerated lockfile stable after newer matching versions are published. highest always picks the newest matching version, which can cause drift whenever the lockfile is rebuilt. The default resolution-mode has changed across pnpm major versions, so inheriting the default is unsafe for reproducibility. Peer-dependency settings (auto-install-peers, dedupe-peer-dependents, workspace-root peer resolution) also alter the final graph independently of resolution-mode, meaning two machines with different pnpm versions or settings can produce different lockfiles even with the same resolution-mode. --frozen-lockfile in CI only detects mismatches; it does not make resolution deterministic across pnpm versions or differing peer settings.
time-based meaningfully reduce churn compared to highest, or does the lockfile's time reference still allow legitimate version advances that defeat the stability goal?lowest-direct is used to pin direct dependencies to their lowest satisfying versions, how often does it pull transitive dependencies old enough to miss security fixes, and is there a practical way to audit that risk without manual inspection?.npmrc that pins both resolution-mode and the peer settings, or is a documented CI checklist sufficient?35275 reputation · 02 Aug 2021, 13:26 UTC
Commit an .npmrc that pins resolution-mode=time-based together with the peer-dependency settings, pin the pnpm major version through packageManager and corepack, and run pnpm install --frozen-lockfile in CI. time-based meaningfully reduces churn between lockfile regenerations, but it is not a freeze: when the lockfile is regenerated monthly, the recorded time reference moves forward, so legitimate in-range updates published before regeneration can still be selected. lowest-direct does not reliably pin old transitive dependencies or guarantee security coverage. A CI checklist is insufficient because local installs and lockfile regeneration can use different pnpm versions or .npmrc values.
Likely explanation. time-based selects the newest matching version that existed at the lockfile's recorded time reference. highest always selects the newest matching version. Between regenerations, time-based suppresses versions published after the reference, while highest can drift on every rebuild. Once the lockfile is regenerated, however, the reference advances and both modes can select the same newly published, in-range version if it predates the new reference. The practical difference is therefore intra-cycle stability, not permanent version freezing.
Confirmed facts. The default resolution-mode differs across pnpm major versions, so inheriting the default is unsafe. Changing the mode on an existing lockfile does not alter already locked entries until the lockfile is regenerated or affected dependencies change. The exact time-reference semantics and whether the reference advances on regeneration should be confirmed for your installed pnpm version.
lowest-direct forces only direct dependencies to their lowest satisfying versions. Transitive dependencies are still resolved by their own ranges under the active resolution mode, so there is no fixed frequency with which it pulls old transitive versions, and it cannot guarantee that security fixes are present. Audit the committed graph in CI with pnpm audit, pnpm outdated, and pnpm ls <package> or pnpm why <package> to locate vulnerable transitive instances without manual tree inspection.
Peer-dependency settings such as auto-install-peers, dedupe-peer-dependents, and strict-peer-dependencies can add, remove, or dedupe packages without changing any semver range. Pin them in the same committed .npmrc as resolution-mode. Also pin packageManager in package.json and use corepack in CI so local and CI pnpm major versions match.
.npmrc:
resolution-mode=time-based
auto-install-peers=true
dedupe-peer-dependents=true
strict-peer-dependencies=true
Omit or adjust peer keys only if your workspace does not use them.package.json:
"packageManager": "pnpm@<major>"
Replace <major> with the exact major your team standardizes on.pnpm-lock.yaml alongside .npmrc.pnpm install --frozen-lockfile to detect drift, then pnpm audit and pnpm outdated for security review.pnpm config get resolution-mode and inspect repo and CI .npmrc to confirm the effective mode and peer keys..npmrc, then compare pnpm-lock.yaml hashes.pnpm --version.Missing diagnostic detail. What pnpm major version runs locally and in CI? If they differ, inherited defaults and lockfile format behavior can differ even with an explicit resolution-mode, which changes whether you must also standardize the pnpm version before trusting the committed lockfile.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 02 Aug 2021, 17:49 UTC
When resolution-mode=time-based is used, pnpm records a timestamp (the lockfile’s generation time) and selects the newest version that existed at or before that timestamp. This stops drift from versions published after the lockfile was created, but it does not block versions that were already published before the timestamp. Consequently, if you regenerate the lockfile monthly, the timestamp moves forward and any in‑range updates released between the old and new timestamps can still be picked up, producing a new lockfile.
Even with identical resolution-mode and peer‑dependency settings, different pnpm major versions may emit different lockfile formats (e.g., changes to how package names are normalized or how the checksums field is laid out). To obtain byte‑identical pnpm-lock.yaml files you must:
packageManager in package.json and use corepack..npmrc (including resolution-mode and peer settings) is read locally and in CI.pnpm install --lockfile-only (or pnpm install --frozen-lockfile in CI) and compare the resulting lockfiles.Verifying these steps guarantees that the lockfile you see in CI is the same one a developer would generate locally, eliminating hidden drift from version‑resolution or format differences.