Open
Conversation
BREAKING CHANGE: root \`preinstall\` now runs before dependencies are installed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BREAKING CHANGE: root `preinstall` now runs before dependencies are installed.
Summary
Moves the root
preinstallscript back to its documented position: before dependencies are installed. Restores behavior that shipped through npm 6 and was unintentionally broken in npm 7 when Arborist took over reify.Fixes #2660.
The bug
The scripts docs have always said
preinstallruns "before the package is installed." Since npm 7, the rootpackage.json'spreinstallhas actually run afterarb.reify()i.e. after every dependency has been resolved, fetched, and unpacked. Arborist explicitly excludes the project root from its rebuild queue (reify.js:1234,!diff.ideal.isProjectRoot), andlib/commands/install.jsappendedpreinstallto the post-reify lifecycle loop along withinstall,postinstall,prepublish,preprepare,prepare,postprepare.npm cihas the same shape.Net effect: there is currently no way to run a script at the root before dependencies hit disk. Projects that want to bootstrap auth, generate files consumed during resolution, or gate installs behind a precondition have had no supported hook for five years.
The fix
Split the root lifecycle loop in two:
preinstallruns via a small helper beforearb.reify().install,postinstall,prepublish,preprepare,prepare,postpreparerun after reify, as they did before.Same split applied to
npm ci.Why now, and why not wait for a lifecycle redesign
This has history:
@ljharb.@wraithgarwith essentially this same code change, then self-closed as "too breaking without broader design work."preinstallordering in the next major.preunpack) and RFC #437 (preinstall revert) opened. Neither progressed. RFC 403'spreunpackhook was rejected in triage as not solving the actual bug.preunpacklifecycle script that runs before installation #8972 opened implementing the rejected RFC 403preunpackapproach. Close but not the right fix.A comprehensive lifecycle redesign is still the right long-term project and has clearly not happened in five years. Gating the revert on it was reasonable in 2021; but has proven that continuing to gate on it in 2026 just means users keep hitting a bug the docs promise doesn't exist. npm 12 is the right window to ship the scoped revert and decouple it from a future lifecycle rewrite.
The code change here is functionally the same as #2713 The differences are: it also patches
npm ci, updates the docs to match reality, adds regression tests, and ships in a major (npm 12) rather than being blocked on a design gate that no longer has owners.Closes / supersedes
When this lands:
preunpacklifecycle script that runs before installation #8972 (supersedes thepreunpackapproach)preunpacklife cycle script rfcs#403 (supersedes —preunpacknot needed)