feat(prebuild): build N-API artifacts, collapsing 36 CI jobs to 4 - #434
Merged
Conversation
cpp/ is pure node-addon-api -- 0 of 133 files reference v8:: -- so the
per-ABI build matrix was never necessary. One N-API binary per
platform/arch/libc serves every Node line and every Electron version.
Matrix before (36 artifacts):
node 3 OS x node 22/24/26 = 9
electron 3 OS x electron 38..43 = 18
alpine node 22/24/26 = 3
alpine electron 38..43 = 6
Matrix after (4 artifacts), exactly the platforms we ship:
linux-x64, win32-x64, darwin-arm64, linuxmusl-x64
The prebuild-electron and prebuild-electron-alpine jobs are removed
outright; Electron consumers are served by the same napi artifact.
package.json
binary.napi_versions declares the ABI floor -- the single source of
truth read by napi-build-utils on both the builder and installer side.
config.runtime=napi flips prebuild-install over. pkg.config takes
precedence over the environment (prebuild-install/rc.js:17), so tooling
that sets npm_config_runtime=electron still resolves the napi artifact.
config.target=8 is load-bearing, not redundant. The napi target swap at
prebuild-install/rc.js:47 is guarded on
`rc.target === process.versions.node`, so anything passing an explicit
target skips it and drops the raw target into the abi slot. Verified
before pinning:
plain npm install -> napi-v8-linux-x64 (ok)
electron-rebuild style -> napi-v43.0.0-linux-x64 (404 -> source build)
explicit node target -> napi-v24.0.0-linux-x64 (404 -> source build)
With config.target pinned, all four scenarios (incl. arm64 and musl)
resolve to napi-v8 with only platform/arch/libc varying.
binding.gyp
NAPI_VERSION=<(napi_build_version)> pins the compiled level to the
advertised floor. Added to the *second* defines block: the target dict
has two "defines" keys, and GYP parses as a Python dict so the later
wins -- BOUNDDATUM_USE_NODE_API in the first block has never reached the
compiler. Confirmed in build/sqlserver.target.mk. Left as-is rather than
merged, since merging would newly enable that define.
napi_build_version% is a backstop only; build/config.gypi already
carries the host Node's level (10 on Node 22) and wins over it. prebuild
passes -Dnapi_build_version=8, which wins over both.
workflow
-r napi is passed explicitly because prebuild's own rc.js hardcodes
runtime:'node' and never reads package.json -- unlike prebuild-install.
--all drives the build from binary.napi_versions rather than from
whichever Node is on the runner.
Verified locally end to end: `npx prebuild -r napi --all --strip` builds
with -DNAPI_VERSION=8 and writes
prebuilds/msnodesqlv8-v5.2.3-napi-v8-linux-x64.tar.gz -- byte-identical in
name to the URL prebuild-install resolves. Addon loads and lib/sql.js
exposes its API against it.
Not yet verified: the Windows, macOS and musl legs, and a real
release-asset round trip. Both need a workflow_dispatch run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
cpp/has been purenode-addon-apisince the NAN migration — 0 of 133 files referencev8::— which means the per-ABI build matrix has been redundant for a while. One N-API binary per platform/arch/libc serves every Node line and every Electron version.Matrix
prebuild-electronandprebuild-electron-alpineare removed outright; Electron consumers are served by the same napi artifact.prebuild.ymlgoes 529 → 352 lines.The 4 artifacts are exactly the platforms we ship:
linux-x64,win32-x64,darwin-arm64,linuxmusl-x64.Verified end to end
Dispatched against this branch (run 32883710731) — all 5 jobs green, 4 assets uploaded to
v5.2.3.Windows, full test suite green against localdb, and the binary is byte-identical to the CI artifact:
That binary was built on Node 22 and run on Node 26.3.0 — the ABI-stability claim demonstrated rather than asserted. Previously this would have required separate
node-v127/node-v137/node-v152builds.Also verified:
prebuild-installfetches and unpacks the linux-x64 artifact (HTTP 200), the addon loads, andlib/sql.jsexposes its API against it.No regression for existing consumers. Published 5.2.3 has neither
confignorbinary(npm view msnodesqlv8@5.2.3 config binary→ empty), so installed copies still resolvemsnodesqlv8-v5.2.3-node-v127-…— confirmed by simulating that package.json against the live release. The 36 pre-existing assets are untouched; the upload was purely additive (40 total = 36 + 4).package.json
binary.napi_versionsdeclares the ABI floor — the single source of truth read bynapi-build-utilson both builder and installer side.config.runtime=napiflipsprebuild-installover.pkg.configtakes precedence over the environment (prebuild-install/rc.js:17), so tooling that setsnpm_config_runtime=electronstill resolves the napi artifact. This is what makes the Electron jobs unnecessary.config.target=8is load-bearing, not redundant. The napi target swap atprebuild-install/rc.js:47is guarded onrc.target === process.versions.node, so anything passing an explicit target skips it and drops the raw target into the abi slot. Measured before pinning:With it pinned, all scenarios (incl. arm64 and musl) resolve to
napi-v8with only platform/arch/libc varying.binding.gyp
NAPI_VERSION=<(napi_build_version)pins the compiled level to the advertised floor, so it is no longer whatever the build headers happen to expose.Added to the second
definesblock deliberately: the target dict has two"defines"keys, and GYP parses the file as a Python dict literal, so the later wins —BOUNDDATUM_USE_NODE_APIin the first block has never reached the compiler. Confirmed againstbuild/sqlserver.target.mk(NODE_GYP_V4present,BOUNDDATUM_USE_NODE_APIabsent). Left as-is rather than merged, since merging would newly enable that define — worth a separate look.napi_build_version%is a backstop only. Resolution order measured: prebuild's-Dnapi_build_version=8wins overbuild/config.gypi(which carries the host Node's level, 10 on Node 22), which wins over the default. Note this means--build-from-sourcedoes not produce an equivalent binary — only the prebuild path is pinned to 8.Workflow
-r napiis passed explicitly because prebuild's ownrc.js:8-10hardcodesruntime: 'node'and never reads package.json — unlikeprebuild-install.--alldrives the build frombinary.napi_versionsrather than from whichever Node is on the runner.Not covered here
darwin-arm64andlinuxmusl-x64are built and correctly named but have not been run against a database.npm-publish.ymlis manual and independent ofprebuild.yml, so publishing 5.2.4 still means separately dispatching Prebuild Binaries for tagv5.2.4. That coupling predates this PR, but the blast radius grows: forgetting it now means every platform compiles from source rather than just some Electron users. A guard asserting napi assets exist for the tag is worth a follow-up.🤖 Generated with Claude Code