Skip to content

feat(deps): adopt better-sqlite3 13, drop install scripts in deps stage - #1934

Merged
steilerDev merged 1 commit into
betafrom
feat/deps-better-sqlite3-13
Aug 2, 2026
Merged

feat(deps): adopt better-sqlite3 13, drop install scripts in deps stage#1934
steilerDev merged 1 commit into
betafrom
feat/deps-better-sqlite3-13

Conversation

@steilerDev

Copy link
Copy Markdown
Owner

Summary

Corrects the analysis in #1927, which held better-sqlite3 at 12.x on the incorrect premise that v13 "always compiles from source". It does not — thanks to @steilerDev for pushing back on this.

What v13 actually changed

v13 moved to N-API and now ships prebuilt binaries inside the npm tarball, rather than downloading them via the (removed) prebuild-install:

package/prebuilds/darwin-arm64.node     package/prebuilds/linuxmusl-arm64.node
package/prebuilds/darwin-x64.node       package/prebuilds/linuxmusl-x64.node
package/prebuilds/linux-arm64.node      package/prebuilds/win32-arm64.node
package/prebuilds/linux-x64.node        package/prebuilds/win32-x64.node

Both Docker target architectures are coveredlinuxmusl-x64 and linuxmusl-arm64 are exactly Alpine on amd64/arm64.

The real cause of #1889's Docker failure

Narrower than I diagnosed. better-sqlite3's package.json declares no install script at all — npm synthesizes node-gyp rebuild because a binding.gyp is present. That build is a no-op when a prebuild exists; binding.gyp says so itself:

# npm's implicit node-gyp rebuild should do nothing when the package
# contains a prebuild for the host. Explicit build scripts override this.
'prebuild_exists%': '<!(node lib/binding.js)',

…and sets type: 'none' when a prebuild is found. But node-gyp still needs python3 just to evaluate gyp. So the image needed python3 for a build that compiles nothing. It was never going to compile SQLite — under emulation or otherwise. (Release images are also built on native per-platform runners, not QEMU — release.yml:120.)

Locally, installing v13 with python3 present takes 0.62s and emits only TOUCH Release/obj.target/better_sqlite3.stamp — no compilation, no .node in build/.

The fix

npm ci --omit=dev --ignore-scripts in the deps stage, rather than adding python3 for a no-op build.

This is safe because better-sqlite3 is the only production dependency with an install script — every other hasInstallScript entry in the lockfile (core-js, cpu-features, fsevents, protobufjs, ssh2, unrs-resolver) is dev: true and excluded by --omit=dev. And it needs no install-time step at all: lib/binding.js resolves prebuilds/<platform>-<arch>.node at require() time.

Also adds a build-time guard asserting a prebuild exists for the target, so a future missing prebuild fails the Docker build instead of crashing the production image at runtime.

Verification (local, arm64 — the harder case)

docker build --target deps:

#13 [deps 9/11] RUN node -e "...getPrebuildPath()..."
#13 0.111 better-sqlite3 prebuild: /app/node_modules/better-sqlite3/prebuilds/linuxmusl-arm64.node
#13 DONE 0.1s

Running inside that image:

musl load OK, row = {"x":7}
sqlite = 3.53.3
glibc  = (none -> musl)

npm ci --omit=dev --ignore-scripts completed in 3.3s.

Also

Removes the now-unnecessary better-sqlite3 semver-major ignore rule from dependabot.yml. The typescript hold stays — that one is real (ts-jest@29.4.x peer is >=4.3 <7).

Test plan

  • CI Quality Gates — the Docker job builds the full image on amd64, exercising the linuxmusl-x64 prebuild path and the new guard
  • arm64 is exercised at release time on its native runner

Corrects the analysis in #1927, which held better-sqlite3 at 12.x on the
incorrect premise that v13 "always compiles from source". It does not.

v13 moved to N-API and now ships prebuilt binaries INSIDE the npm tarball
(prebuilds/{darwin,linux,linuxmusl,win32}-{x64,arm64}.node) rather than
downloading them via the removed prebuild-install dependency. Both Docker
target architectures are covered by linuxmusl-x64 and linuxmusl-arm64.

The Docker failure in #1889 was narrower than diagnosed: better-sqlite3's
package.json declares no install script at all — npm *synthesizes*
`node-gyp rebuild` because a binding.gyp is present. That build is a no-op
when a prebuild exists (binding.gyp resolves `prebuild_exists` by running
`node lib/binding.js` and sets target type 'none'), but node-gyp still needs
python3 merely to evaluate gyp. So the image needed python3 for a build that
compiles nothing — it was never going to compile SQLite, under emulation or
otherwise. Release images are also built on native per-platform runners
(release.yml), not QEMU.

Fix: `npm ci --omit=dev --ignore-scripts` in the deps stage. better-sqlite3
is the only production dependency with an install script — every other
`hasInstallScript` package in the lockfile is dev-only and excluded by
--omit=dev — and it needs no install-time step, since lib/binding.js
resolves prebuilds/<platform>-<arch>.node at require() time. This keeps the
image toolchain-free rather than adding python3 for a no-op.

Adds a build-time guard asserting a prebuild exists for the target, so a
future missing prebuild fails the Docker build instead of crashing the
production image at runtime.

Verified locally: `docker build --target deps` resolves
prebuilds/linuxmusl-arm64.node, and the binary loads and executes queries
inside the Alpine image (musl confirmed via glibcVersionRuntime = none).

Also removes the now-unnecessary better-sqlite3 semver-major ignore rule.
The typescript hold stays — that one is real (ts-jest peer is ">=4.3 <7").
@steilerDev
steilerDev merged commit 8a831a4 into beta Aug 2, 2026
30 of 31 checks passed
@steilerDev
steilerDev deleted the feat/deps-better-sqlite3-13 branch August 2, 2026 08:43
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0-beta.41 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant