You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
#1959's shrinkwrap prune breaks npm 11 consumers' npm ci (5.2.0+): alasql → react-native-fs left unresolved #2941
#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 working npm ci is not optional. npm 12 installs already get the tree regardless.
Fix the dependency itself. Upstream already has the right change: Make react-native-fs an optional peer dependency AlaSQL/alasql#2456 moves react-native-fs from optionalDependencies to 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/alasql carrying that one package.json change on top of 4.17.3, and drop the fork once upstream releases. The change is metadata-only, because alasql already guards every require('react-native-fs') behind isReactNative, 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.
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.
Summary
#1959 broke
npm cifor 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-fsand its tree out of the publishednpm-shrinkwrap.json. alasql still declaresreact-native-fsas anoptionalDependency, 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 installtolerates that, whilenpm 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 bynpm ci.Repro
react-native-fsin shrinkwrapnpm cion node:24 (npm 11.19)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
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.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.@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.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.Preventing a repeat
Add a shrinkwrap consistency check to the build, next to
check-shrinkwrap-pins.mjs, that fails if any declareddependenciesoroptionalDependenciesedge in the generatednpm-shrinkwrap.jsondoesn't resolve. That is exactly the invariantnpm cienforces, 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