Summary
harper ≥ 5.1.18 publishes an npm-shrinkwrap.json (5.1.9 did not). When a project depends on harper and its lockfile is generated with npm 11 (bundled with current Node 24 releases), npm folds harper's ~1,210-package shrinkwrap subtree into the consumer's package-lock.json nested under node_modules/harper/ — and drops the "optional": true flag from platform-specific packages (@esbuild/*, pulled in via tsx → esbuild).
npm ci then treats @esbuild/aix-ppc64 as a required package on every platform and fails:
npm error code EBADPLATFORM
npm error notsup Unsupported platform for @esbuild/aix-ppc64@0.28.1:
wanted {"os":"aix","cpu":"ppc64"} (current: {"os":"linux","cpu":"x64"})
To be clear about fault: harper's shrinkwrap itself is correct (it marks these packages "optional": true); the flag is lost in npm 11's reify. npm 12.0.1 handles it properly (the nested subtree collapses to a single entry). But since npm 11 is what Node 24 ships, every consumer with harper as an npm dependency and a stock CI image hits this.
Independent repro
On any macOS/Linux machine with Node 24 + npm 11 (verified with npm 11.8.0 and 11.13.0):
mkdir repro && cd repro
npm init -y
npm install harper@5.1.19 # or 5.1.18 — both ship the shrinkwrap
# lockfile is now poisoned:
node -e "
const l = require('./package-lock.json');
const e = l.packages['node_modules/harper/node_modules/@esbuild/aix-ppc64'];
console.log('entry exists:', !!e, '| optional flag:', e && e.optional);"
# → entry exists: true | optional flag: undefined
rm -rf node_modules
npm ci # → EBADPLATFORM (unless you happen to be on AIX/ppc64)
Control: repeat with npx npm@12 install — the lockfile keeps only one harmless nested entry and npx npm@12 ci succeeds. Repeat with harper@5.1.9 (no shrinkwrap) — no nested entries at all, npm 11 works fine.
Impact
Any downstream project that has harper in package.json and runs npm ci in CI with npm 11 (the GitHub Actions default via setup-node + Node 24) gets a hard install failure on every run. Regenerating the lockfile doesn't help — npm 11 reintroduces the bad entries every time.
Options
- Stop publishing
npm-shrinkwrap.json (return to ≤ 5.1.9 behavior) — consumers get normal resolution; this is what npm docs recommend for libraries (shrinkwrap is designed for CLIs installed standalone, and harper is both, which is the tension here).
- Keep it but document that consumers need npm ≥ 12 (engines can't express "the npm that installs your dependents", so this is docs-only).
- Upstream: this is arguably an npm/cli reify bug worth filing there too — happy to do that with the repro above if useful.
Workaround we're using in the consumer repo meanwhile: regenerate package-lock with npm 12, install npm 12 in CI before npm ci, and set engines.npm >= 12 + engine-strict=true so an npm 11 developer install can't silently re-poison the lockfile.
Summary
harper ≥ 5.1.18 publishes an
npm-shrinkwrap.json(5.1.9 did not). When a project depends on harper and its lockfile is generated with npm 11 (bundled with current Node 24 releases), npm folds harper's ~1,210-package shrinkwrap subtree into the consumer'spackage-lock.jsonnested undernode_modules/harper/— and drops the"optional": trueflag from platform-specific packages (@esbuild/*, pulled in viatsx → esbuild).npm cithen treats@esbuild/aix-ppc64as a required package on every platform and fails:To be clear about fault: harper's shrinkwrap itself is correct (it marks these packages
"optional": true); the flag is lost in npm 11's reify. npm 12.0.1 handles it properly (the nested subtree collapses to a single entry). But since npm 11 is what Node 24 ships, every consumer with harper as an npm dependency and a stock CI image hits this.Independent repro
On any macOS/Linux machine with Node 24 + npm 11 (verified with npm 11.8.0 and 11.13.0):
Control: repeat with
npx npm@12 install— the lockfile keeps only one harmless nested entry andnpx npm@12 cisucceeds. Repeat withharper@5.1.9(no shrinkwrap) — no nested entries at all, npm 11 works fine.Impact
Any downstream project that has
harperin package.json and runsnpm ciin CI with npm 11 (the GitHub Actions default via setup-node + Node 24) gets a hard install failure on every run. Regenerating the lockfile doesn't help — npm 11 reintroduces the bad entries every time.Options
npm-shrinkwrap.json(return to ≤ 5.1.9 behavior) — consumers get normal resolution; this is what npm docs recommend for libraries (shrinkwrap is designed for CLIs installed standalone, and harper is both, which is the tension here).Workaround we're using in the consumer repo meanwhile: regenerate package-lock with npm 12, install npm 12 in CI before
npm ci, and setengines.npm >= 12+engine-strict=trueso an npm 11 developer install can't silently re-poison the lockfile.