Skip to content

fix(arborist): honor gypfile:false on lockfile-driven installs - #9859

Open
lazerg wants to merge 2 commits into
npm:latestfrom
lazerg:fix/9837-gypfile-lockfile-nodes
Open

fix(arborist): honor gypfile:false on lockfile-driven installs#9859
lazerg wants to merge 2 commits into
npm:latestfrom
lazerg:fix/9837-gypfile-lockfile-nodes

Conversation

@lazerg

@lazerg lazerg commented Aug 8, 2026

Copy link
Copy Markdown

Summary

gypfile: false opts a package out of the node-gyp rebuild install script npm synthesises when it finds a binding.gyp. That opt-out only survives when the tree node was built from a packument or read off disk. Nodes built from a lockfile carry no gypfile field at all, so on npm ci, or on any npm install with an existing package-lock.json, npm sees the binding.gyp and adds the script anyway.

With better-sqlite3@13.0.2 that shows up as a spurious install-scripts warning. It is not only cosmetic: the synthesised script is real, so once it is covered by allowScripts (or on npm 10, which has no such gate) node-gyp rebuild actually runs against a package that ships prebuilt binaries and asked not to be rebuilt.

Both the script enumeration in install-scripts.js and the build set in rebuild.js now read the opt-out back from the installed package.json when the node itself does not know it.

Fixes #9837

Testing

Against the repro in the issue, npm ci no longer warns, and npm ci --dangerously-allow-all-scripts --foreground-scripts no longer invokes node-gyp. Added regression tests for both paths.

@lazerg
lazerg requested review from a team as code owners August 8, 2026 19:30
if (
!hasExplicitGypGate &&
pkg.gypfile !== false &&
await isNodeGypPackage(node.path).catch(() => false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInstallScripts() also runs during the strict allow-scripts preflight, before reify replaces the existing node_modules entry. At that point, node.path can refer to a different version.

For example, suppose the installed version has binding.gyp and gypfile: false, while the locked version has binding.gyp without the opt-out. This reads false from the installed version, so strict preflight passes. Rebuild later sees the new package and synthesizes node-gyp rebuild.

Can we check that the package on disk matches the node's name and version before trusting this fallback?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that is real. The preflight walks the ideal tree, so node.path still points at whatever version is installed right now.

hasGypfileOptOut now takes the node's package object instead of only the gypfile value, and it trusts the disk read only when the package.json at that path carries the same name and version as the node. If either one differs, or the node has no name or version to compare against, the opt-out counts as absent and node-gyp rebuild is enumerated. That is what npm did before this fallback existed, so the preflight can no longer come out quieter than the later rebuild.

Added three cases in workspaces/arborist/test/install-scripts.js: a version mismatch on disk, a name mismatch, and a node with nothing to match on.

@JamieMagee JamieMagee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post-install warning and rebuild paths look correct. I found one issue in strict preflight, where the fallback can read metadata from a stale installed version.

The strict allow-scripts preflight walks the ideal tree before reify
replaces node_modules, so the package.json at a node's path can still
belong to a different version. Reading gypfile: false from it made the
preflight pass while the later rebuild, which sees the real package,
synthesised node-gyp rebuild anyway.

Compare the name and the version on disk against the node before the
opt-out counts. If either is missing or differs, treat the opt-out as
absent.
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] Incorrect detection of install script for better-sqlite3@13.0.2?

2 participants