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
alasql's optional react-native-fs dep pulls ~140M of react-native/hermes/metro into installs and published Docker images #1937
alasql (4.17.x) declares optionalDependencies: { "react-native-fs": "^2.20.0" }. react-native-fs itself is 828K and unused in Node (alasql only requires it behind a guard for RN environments), but it has a peer dependency on react-native — and npm 7+ auto-installs peer deps. So every plain npm install harper pulls in react-native@0.82, react@19, hermes, metro, and react-devtools.
Confirmed in published Docker images
harperfast/harper:latest (5.1.23) ships:
package
size
hermes-compiler
47M
react-native
37M
@react-native/*
25M
react-devtools-core
17M
metro*
~9M
react-native-fs, react, hermes-parser, misc
~4M
~140M of the 373M node_modules (~38%) is the react-native tree. The Dockerfile installs the packed tarball with plain npm install --ignore-scripts --global, which includes optional deps and auto-installed peers.
Not Docker-specific: every npm install of harper 5.x gets the same ~140M. (The legacy harperdb/harperdb 4.x images are unaffected only because they ship alasql 4.6.6, which predates the optional dep.)
Beyond size, this puts metro/hermes/react-devtools in a production database image — pure CVE-scanner noise and supply-chain surface for code that can never execute.
Fix
Do not use --omit=optional in the Dockerfile — harper's own tree relies on optionalDependencies for the @harperfast/rocksdb-js-* platform prebuilds, cbor/msgpackr extractors, bufferutil, utf-8-validate, etc.
Recommended: add an npm override in package.json, next to the existing alasql → xlsx override, stubbing react-native-fs out with a tiny empty package (e.g. noop2 or an internal empty stub):
This kills the chain at the root for Docker, npm, and dev installs alike.
Longer term: upstream a peerDependenciesMeta: { "react-native": { "optional": true } } fix to react-native-fs, and/or ask alasql to drop the optional dep.
That works when our package is the install root, so it looked fixed in our repo and in CI. It is ignored entirely for anyone installing our published package, because npm only applies overrides from the root of the install tree — and a consumer's root is their project, not ours.
Verified against a fresh production install of our published artifact:
So any package that depends on Harper inherits the subtree and has no supported way to shed it. Alongside the size, the subtree carries advisories into production installs — js-yaml (ReDoS via merge keys) plus the @react-native/*, babel-jest, @jest/transform and chromium-edge-launcher tree.
Not urgent from our side, and no expectation attached — just closing the loop that the downstream escape hatch people would naturally reach for doesn't exist here.
node_modules/react-native-fs is absent from the file. So the manifest declares an optional dependency it does not resolve, and my guess was that npm re-resolves it from the registry.
I tested that by editing the shrinkwrap to remove the dangling optionalDependencies key, repacking and installing. No change — 450.4 MB, react-native still present. The hypothesis is refuted.
If the published npm-shrinkwrap.json were authoritative for this install, yargs would be exactly 16.2.0. It is not. npm performs a fresh resolution and the shipped shrinkwrap does not constrain the consumer's tree at all.
That explains both observations: pruning produces a clean artifact, and no consumer's tree changes — because nothing reads it on this path. It is also consistent with part 2 helping the Docker image, since the image build is a different install path.
Why no lockfile change can fix this
The requirement lives in a place a lockfile cannot reach:
react-native is not marked optional in peerDependenciesMeta, while react-native-windows is. A shrinkwrap constrains which version resolves; it cannot un-declare a dependency a transitive package declares in its own published manifest.
overrides does not help either — it applies only to the root project, so it never reaches anyone installing harper as a dependency, and it does not apply to the package's own manifest on a global install.
Levers that would actually work
react-native-fs marks its react-native peer optional. One line. Its peerDependenciesMeta already marks react-native-windows optional, so the omission reads as an oversight. Cheapest fix, furthest upstream.
alasql drops the optional dependency. It only requires it behind an isReactNative guard, so it is unreachable for every Node consumer.
Patch or vendor alasql. The only lever fully within this project's control.
Happy to supply anything further — the repro above is three lines and the yargs control is the part I would keep.
Activity
tps-flint commentedon Jul 26, 2026
Additional evidence from the consumer side, in case it helps prioritise: this cannot be worked around downstream.
We tried the obvious mitigation — stubbing the transitive dependency out via npm
overridesin our ownpackage.json:That works when our package is the install root, so it looked fixed in our repo and in CI. It is ignored entirely for anyone installing our published package, because npm only applies
overridesfrom the root of the install tree — and a consumer's root is their project, not ours.Verified against a fresh production install of our published artifact:
So any package that depends on Harper inherits the subtree and has no supported way to shed it. Alongside the size, the subtree carries advisories into production installs —
js-yaml(ReDoS via merge keys) plus the@react-native/*,babel-jest,@jest/transformandchromium-edge-launchertree.Not urgent from our side, and no expectation attached — just closing the loop that the downstream escape hatch people would naturally reach for doesn't exist here.
docs(design): record how the published shrinkwrap governs installs
fix(build): only sever optional react-native-fs edges, and verify the…
test(build): pin the prune backstop, nested/scoped resolution, and pr…
test(build): cover nested react-native-fs resolution and the ancestor…
fix(build): strip the react-native tree from the published shrinkwrap
docs(design): record how the published shrinkwrap governs installs
fix(build): only sever optional react-native-fs edges, and verify the…
test(build): pin the prune backstop, nested/scoped resolution, and pr…
test(build): cover nested react-native-fs resolution and the ancestor…
tps-flint commentedon Jul 31, 2026
Empirical follow-up on why #1959 (part 1) does not reduce a consumer's tree, with a control that isolates the cause.
Tested against
harper@5.2.0-beta.4, published 2026-07-31T03:42Z — after #1959 merged on 2026-07-28T19:39Z, so the fix is in this build.The published artifact is genuinely clean
Part 1 did exactly what it set out to do.
A consumer install is unchanged
450.6 MB, 501 top-level entries. For comparison,
harper@5.1.22is 445.0 MB — so 5.2.0-beta.4 is marginally larger.First hypothesis, and it was wrong
The shrinkwrap's
alasqlentry still carries the declaration whose target was pruned:node_modules/react-native-fsis absent from the file. So the manifest declares an optional dependency it does not resolve, and my guess was that npm re-resolves it from the registry.I tested that by editing the shrinkwrap to remove the dangling
optionalDependencieskey, repacking and installing. No change — 450.4 MB,react-nativestill present. The hypothesis is refuted.The control that identifies the actual cause
If the published
npm-shrinkwrap.jsonwere authoritative for this install,yargswould be exactly16.2.0. It is not. npm performs a fresh resolution and the shipped shrinkwrap does not constrain the consumer's tree at all.That explains both observations: pruning produces a clean artifact, and no consumer's tree changes — because nothing reads it on this path. It is also consistent with part 2 helping the Docker image, since the image build is a different install path.
Why no lockfile change can fix this
The requirement lives in a place a lockfile cannot reach:
react-nativeis not marked optional inpeerDependenciesMeta, whilereact-native-windowsis. A shrinkwrap constrains which version resolves; it cannot un-declare a dependency a transitive package declares in its own published manifest.overridesdoes not help either — it applies only to the root project, so it never reaches anyone installingharperas a dependency, and it does not apply to the package's own manifest on a global install.Levers that would actually work
react-native-fsmarks itsreact-nativepeer optional. One line. ItspeerDependenciesMetaalready marksreact-native-windowsoptional, so the omission reads as an oversight. Cheapest fix, furthest upstream.alasqldrops the optional dependency. It onlyrequires it behind anisReactNativeguard, so it is unreachable for every Node consumer.alasql. The only lever fully within this project's control.Happy to supply anything further — the repro above is three lines and the
yargscontrol is the part I would keep.chore: back out the @tpsdev-ai/harper reprint, pin upstream harper (#…
npm ci(5.2.0+): alasql → react-native-fs left unresolved #2941