Repository navigation
#1959's shrinkwrap prune breaks npm 11 consumers' npm ci (5.2.0+): alasql → react-native-fs left unresolved #2941
Description
Summary
#1959 broke npm ci for every harper consumer on the npm that ships with Node. That covers Node 24 and Node 26, which both bundle npm 11. It first shipped in 5.2.0-beta.3 and is in every 5.2.x and 5.3.0 release. 5.1.x never got it and is unaffected.
#1959 prunes react-native-fs and its tree out of the published npm-shrinkwrap.json. alasql still declares react-native-fs as an optionalDependency, so the published tree has a dangling edge. That is not a bug in how the prune was written. The approach itself is wrong: a lockfile has no way to express "this declared optional dependency is intentionally absent". npm records optional deps even when they can't be installed. So deleting the entry while alasql still declares it always produces a tree npm would never write.
The prune's own guard reflects the flawed assumption: it rejects unresolved required edges but deliberately allows unresolved optional ones. npm install tolerates that, while npm ci's lockfile check rejects it. This was an incorrect way to reduce package size, and it shipped without anyone testing a consumer install followed by npm ci.
Repro
docker run --rm node:24 bash -c '
cd $(mktemp -d) && npm init -y >/dev/null
npm install --ignore-scripts harper@5.3.0
rm -rf node_modules
npm ci --ignore-scripts'npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. ...
npm error Missing: react-native-fs@2.20.0 from lock file
npm error Missing: react-native@0.84.1 from lock file
| harper | alasql | react-native-fs in shrinkwrap |
npm ci on node:24 (npm 11.19) |
|---|---|---|---|
| 5.1.29 | 4.17.3 | present | ✅ |
| 5.2.0-beta.3 … 5.2.14 | 4.17.3 | pruned | ❌ EUSAGE |
| 5.3.0 | 4.17.3 | pruned | ❌ EUSAGE (same on node:26, npm 11.19.1) |
alasql didn't change between 5.1 and 5.2. The only difference is the prune.
This is separate from #1780 (dev entries in the 5.1.18/5.1.19 shrinkwrap, fixed by #1783), but the effect is the same: harper doesn't install cleanly with the npm that Node ships. Downstream, HarperFast/engineering-metrics had to require npm 12, which only "works" because npm 12 ignores the shrinkwrap entirely (#2172). HarperFast/engineering-metrics#368 removes that requirement.
Fix
- Revert fix(build): strip the react-native tree from the published shrinkwrap #1959 in the next 5.2 and 5.3 patches. 5.1.29 already shows what that gives us: an unpruned shrinkwrap that is internally consistent and passes
npm ci. The react-native tree comes back for npm 11 installs until step 2 lands. That is the correct trade-off, because size is a nice-to-have and a workingnpm ciis not optional. npm 12 installs already get the tree regardless. - Fix the dependency itself. Upstream already has the right change: Make
react-native-fsan optional peer dependency AlaSQL/alasql#2456 movesreact-native-fsfromoptionalDependenciesto an optional peer dependency (peerDependenciesMeta). npm then never installs it, and the shrinkwrap is consistent with no pruning. The maintainer accepted it for alasql's next major and said on 2026-09-29 that it would land in about two weeks.- If that holds, bump to the new alasql major.
- If it slips, publish a fork as
@harperfast/alasqlcarrying that onepackage.jsonchange on top of 4.17.3, and drop the fork once upstream releases. The change is metadata-only, because alasql already guards everyrequire('react-native-fs')behindisReactNative, so the maintenance cost is near zero. - Either way this removes the react-native tree for npm 11 and npm 12 alike, which the prune never did for npm 12.
- Stop publishing the shrinkwrap (Stop publishing npm-shrinkwrap.json: ignored by npm 12 (Node 27), and it has broken consumer npm ci twice #2942). npm 12 ignores it, and Node 27 is the first Node release that bundles npm 12 (deps: upgrade npm to 12.1.0 nodejs/node#66212). The load-bearing deps are already exact-pinned in
package.json(Pin the load-bearing native and encoder dependencies #2179), and this is the second time the shrinkwrap has broken consumernpm ci(npm-shrinkwrap.json (5.1.18+) breaks consumers'npm cion npm 11 — EBADPLATFORM @esbuild/aix-ppc64 #1780, then this issue). Dropping it removes this whole class of bug, and the consistency check below along with it. - Long term, remove alasql (Legacy SQL stack (alasql, mathjs, moment) is ~33MB of every install and the root cause of the react-native tree #2606). That is the real cause of the footprint, but it's a much bigger lift and shouldn't block 1–3.
Preventing a repeat
Add a shrinkwrap consistency check to the build, next to check-shrinkwrap-pins.mjs, that fails if any declared dependencies or optionalDependencies edge in the generated npm-shrinkwrap.json doesn't resolve. That is exactly the invariant npm ci enforces, and it would have failed #1959 at build time. It is the same resolver the prune script already uses, applied to all edges instead of only required ones.
A local tarball install is not a substitute for that check: npm ignores the shrinkwrap in npm install ./harper-x.tgz, so the tarball install passes even for 5.3.0.
After publishing, the release workflow should also run the repro above against the just-published version on the bundled npm of each supported Node. That checks the real consumer path end to end.
sent with Claude Opus 5.5
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
[-]Published shrinkwrap (5.2.0+) leaves alasql → react-native-fs unresolved; npm 11 consumers' `npm ci` fails with EUSAGE[/-][+]#1959's shrinkwrap prune breaks npm 11 consumers' `npm ci` (5.2.0+): alasql → react-native-fs left unresolved[/+]kriszyp commentedon Oct 13, 2026
Fixed by #3030 (Publish a portable locked JavaScript dependency bundle), merged 2026-10-07, which removes the pruned shrinkwrap; cherry-picked to v5.3 as #3079. 5.2.x is not being patched. Closing; the merged PR referenced this issue without a closing keyword.
— Claude Opus 5.5 (triage pass)