Skip to content

Conversation

@DukeDeSouth
Copy link

Summary

Since npm v7, the preinstall script runs after dependencies are installed — making it functionally identical to postinstall and eliminating the ability to run scripts before installation begins. This has been an open bug for 5 years (#2660, 142 reactions, 72 comments, Priority 1 label).

This PR implements RFC #403 by adding a new preunpack lifecycle script that runs BEFORE arb.reify() — i.e., before any dependencies are fetched or installed.

Problem

Current npm install flow:
  arb.reify()          ← ALL deps installed here
  preinstall           ← runs AFTER deps (useless for pre-install tasks)
  install
  postinstall
  prepare

Users cannot:

  • Authenticate to private registries before fetching deps (AWS CodeArtifact, Azure, GCP)
  • Run bootstrap/setup scripts (gRPC workspace generation, environment validation)
  • Enforce package manager restrictions
  • Block malicious dependency code from executing

Solution

New npm install flow:
  preunpack            ← NEW: runs BEFORE any installation
  arb.reify()          ← deps installed
  preinstall           ← unchanged (backwards compatible)
  install
  postinstall
  prepare

Behavior

Scenario preunpack runs?
npm install (no args, local) Yes
npm ci Yes
npm install <pkg> No
npm install -g No
--ignore-scripts No
Script exits non-zero Install aborted
No preunpack in package.json No-op (zero impact)

Example usage

{
  "scripts": {
    "preunpack": "aws codeartifact login --tool npm --repository my-repo --domain my-domain"
  }
}

Changes

File Change
lib/commands/install.js Add preunpack execution before Arborist require
lib/commands/ci.js Add preunpack execution before arb.reify()
lib/commands/run.js Add preunpack to known lifecycle scripts list
docs/.../scripts.md Document preunpack event and update operation order
test/.../install.js 3 new tests + update lifecycle order assertion
test/.../ci.js 2 new tests + update lifecycle order assertion

+239 lines, -2 lines. Non-breaking change.

Prior Art

  • RFC #403 — Accepted RFC for preunpack (discussed in npm meeting 2021-07-21)
  • PR #2713 — Attempted to move preinstall before reify (closed: breaking change)
  • PR #5264 — Proposed prefetch script (closed: no response)
  • PR #6888 — Proposed preinstallOnly (closed: abandoned)
  • Internal branch owlstronaut/feat-preunpack (2025-06-19) — Started implementation but never merged

Test Plan

  • npm install — preunpack runs first in lifecycle order (9/9 tests pass)
  • npm ci — preunpack runs first in lifecycle order (12/12 tests pass)
  • npm install — preunpack runs before arb.reify()
  • npm ci — preunpack runs before arb.reify()
  • npm install <pkg> — preunpack does NOT run
  • --ignore-scripts — preunpack does NOT run
  • All existing tests pass (0 regressions)

Fixes #2660
Implements npm/rfcs#403

Made with Cursor

Adds a new `preunpack` lifecycle script that runs BEFORE dependencies are
installed. This provides a hook for setup tasks that must happen before any
packages are fetched, such as authenticating to private registries or
validating environment prerequisites.

Since npm v7, the `preinstall` script runs AFTER dependencies are installed
(alongside `install` and `postinstall`), eliminating the ability to run
scripts before the installation begins. This has been a pain point for
5 years (142 reactions, 72 comments on npm#2660).

The `preunpack` script:
- Runs on local `npm install` (without arguments) and `npm ci`
- Does NOT run for specific package installs, global installs, or
  when --ignore-scripts is set
- Aborts installation if the script exits with a non-zero code
- Is listed as a known lifecycle script in `npm run` output

Implements: npm/rfcs#403
Fixes: npm#2660
Co-authored-by: Cursor <cursoragent@cursor.com>
@DukeDeSouth DukeDeSouth requested a review from a team as a code owner February 10, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Preinstall script runs after installing dependencies

1 participant