Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 75 additions & 30 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,19 @@ on:
env:
# See https://github.com/nodejs/release#release-schedule
# Node.js v20 EOL = 2026-04-30. v22 EOL = 2027-04-30. v23 EOL = 2025-06-01. v24 EOL = 2028-04-30. v25 EOL = 2026-06-01. v26 EOL = TBD.
# prebuildify writes per-ABI binaries into prebuilds/<platform>-<arch>/;
# each job uploads that directory as an artifact and the publish job merges
# them all into the npm package (no more GitHub-release download step).
# Linux jobs append --tag-libc so glibc and musl binaries can coexist in the
# same prebuilds/linux-<arch>/ directory.
# --no-napi: better-sqlite3 uses V8 APIs directly, so binaries are per-ABI.
# --name node: emit canonical node-gyp-build names (node.abi<N>[.<libc>].node)
# instead of prebuildify's package-name default, which would drop the
# runtime/abi tags that node-gyp-build matches on.
# Node.js 20-24 can build with GCC 10 (bullseye)
NODE_BUILD_CMD_LEGACY: npx --no-install prebuild -r node -t 20.0.0 -t 22.0.0 -t 23.0.0 -t 24.0.0 --include-regex '_sqlite3'
NODE_BUILD_CMD_LEGACY: npx --no-install prebuildify --strip --no-napi --name node -t 20.0.0 -t 22.0.0 -t 23.0.0 -t 24.0.0
# Node.js 25+ requires GCC 11+ for <source_location> header (bookworm)
NODE_BUILD_CMD_MODERN: npx --no-install prebuild -r node -t 25.0.0 -t 26.0.0 --include-regex '_sqlite3'
NODE_BUILD_CMD_MODERN: npx --no-install prebuildify --strip --no-napi --name node -t 25.0.0 -t 26.0.0

jobs:
test:
Expand Down Expand Up @@ -147,14 +156,20 @@ jobs:
with:
node-version: 20
registry-url: https://registry.npmjs.org
- name: Collect prebuilds from all platforms
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: prebuilds-*
path: prebuilds
merge-multiple: true
- name: Show collected prebuilds
run: ls -lR prebuilds
- name: Upgrade npm for OIDC support
run: npm install -g npm@latest
- run: npm publish --provenance

prebuild:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
strategy:
fail-fast: false
matrix:
Expand All @@ -175,19 +190,25 @@ jobs:
- if: ${{ startsWith(matrix.os, 'macos') }}
run: brew install python-setuptools icu4c
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_MODERN }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_LEGACY }}
- run: ${{ env.NODE_BUILD_CMD_MODERN }}
- run: node deps/copy-shell.js
- if: matrix.os == 'windows-2022'
run: |
${{ env.NODE_BUILD_CMD_LEGACY }} --arch ia32 -u ${{ secrets.GITHUB_TOKEN }}
${{ env.NODE_BUILD_CMD_MODERN }} --arch ia32 -u ${{ secrets.GITHUB_TOKEN }}
${{ env.NODE_BUILD_CMD_LEGACY }} --arch arm64 -u ${{ secrets.GITHUB_TOKEN }}
${{ env.NODE_BUILD_CMD_MODERN }} --arch arm64 -u ${{ secrets.GITHUB_TOKEN }}
${{ env.NODE_BUILD_CMD_LEGACY }} --arch ia32
${{ env.NODE_BUILD_CMD_MODERN }} --arch ia32
node deps/copy-shell.js --arch ia32
${{ env.NODE_BUILD_CMD_LEGACY }} --arch arm64
${{ env.NODE_BUILD_CMD_MODERN }} --arch arm64
node deps/copy-shell.js --arch arm64
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-${{ matrix.os }}
path: prebuilds
if-no-files-found: error

prebuild-linux-x64:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on Linux x64
runs-on: ubuntu-latest
container: node:20-bullseye
Expand All @@ -196,12 +217,16 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: apt-get update && apt-get install -y libicu-dev
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} --tag-libc
- run: node deps/copy-shell.js
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-linux-x64
path: prebuilds
if-no-files-found: error

prebuild-linux-x64-node-modern:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on Linux x64 (Node 25+)
runs-on: ubuntu-latest
container: node:20-bookworm
Expand All @@ -210,12 +235,15 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: apt-get update && apt-get install -y libicu-dev
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_MODERN }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_MODERN }} --tag-libc
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-linux-x64-node-modern
path: prebuilds
if-no-files-found: error

prebuild-alpine:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on alpine
runs-on: ubuntu-latest
container: node:20-alpine
Expand All @@ -224,8 +252,15 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: apk add build-base git python3 py3-setuptools libstdc++ readline-dev ncurses-dev icu-dev --update-cache
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_MODERN }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} --tag-libc
- run: ${{ env.NODE_BUILD_CMD_MODERN }} --tag-libc
# No copy-shell here: the zero_sqlite3 shell target is not built on
# alpine (type "none" in binding.gyp).
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-alpine
path: prebuilds
if-no-files-found: error

# arm64 prebuilds run on native GitHub-hosted ARM runners (ubuntu-24.04-arm,
# free for public repos) instead of QEMU emulation. This is faster and avoids
Expand All @@ -238,8 +273,6 @@ jobs:
# QEMU.
prebuild-alpine-arm64:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on alpine (arm64)
runs-on: ubuntu-24.04-arm
needs: test
Expand All @@ -250,13 +283,16 @@ jobs:
apk add build-base git python3 py3-setuptools libstdc++ readline-dev ncurses-dev icu-dev --update-cache && \
cd /tmp/project && \
npm install --ignore-scripts && \
${{ env.NODE_BUILD_CMD_LEGACY }} -u ${{ secrets.GITHUB_TOKEN }} && \
${{ env.NODE_BUILD_CMD_MODERN }} -u ${{ secrets.GITHUB_TOKEN }}"
${{ env.NODE_BUILD_CMD_LEGACY }} --tag-libc && \
${{ env.NODE_BUILD_CMD_MODERN }} --tag-libc"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-alpine-arm64
path: prebuilds
if-no-files-found: error

prebuild-linux-arm64:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on Linux arm64
runs-on: ubuntu-24.04-arm
container: node:20-bullseye
Expand All @@ -265,12 +301,16 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: apt-get update && apt-get install -y libicu-dev
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_LEGACY }} --tag-libc
- run: node deps/copy-shell.js
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-linux-arm64
path: prebuilds
if-no-files-found: error

prebuild-linux-arm64-node-modern:
if: ${{ github.event_name == 'release' }}
permissions:
contents: write # prebuild -u uploads the .node assets to the GitHub release
name: Prebuild on Linux arm64 (Node 25+)
runs-on: ubuntu-24.04-arm
container: node:20-bookworm
Expand All @@ -279,4 +319,9 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: apt-get update && apt-get install -y libicu-dev
- run: npm install --ignore-scripts
- run: ${{ env.NODE_BUILD_CMD_MODERN }} -u ${{ secrets.GITHUB_TOKEN }}
- run: ${{ env.NODE_BUILD_CMD_MODERN }} --tag-libc
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: prebuilds-linux-arm64-node-modern
path: prebuilds
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ coverage

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/
prebuilds/

# Dependency directory
node_modules
Expand Down
35 changes: 35 additions & 0 deletions deps/copy-shell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';
// Copies the zero_sqlite3 shell executable produced by node-gyp into the
// prebuilds/<platform>-<arch>/ directory populated by prebuildify, so the
// shell ships inside the npm package next to the prebuilt .node addons
// (prebuildify itself only ships .node files). The shell is a standalone
// C executable, so unlike the addon it is not Node-ABI-specific.
//
// Usage: node deps/copy-shell.js [--arch <arch>]
// --arch is needed for cross-compiled builds (e.g. win32 ia32/arm64), where
// process.arch reports the host arch, not the target arch.
const fs = require('fs');
const path = require('path');

const archFlagIndex = process.argv.indexOf('--arch');
let arch = process.arch;
if (archFlagIndex !== -1) {
arch = process.argv[archFlagIndex + 1];
if (!arch || arch.startsWith('--')) {
console.error('copy-shell: --arch requires a value (e.g. --arch ia32)');
process.exit(1);
}
}
const binaryName = process.platform === 'win32' ? 'zero_sqlite3.exe' : 'zero_sqlite3';
const src = path.join(__dirname, '..', 'build', 'Release', binaryName);
const destDir = path.join(__dirname, '..', 'prebuilds', `${process.platform}-${arch}`);
const dest = path.join(destDir, binaryName);

if (!fs.existsSync(src)) {
console.error(`copy-shell: ${src} does not exist (was the shell built?)`);
process.exit(1);
}
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(src, dest);
fs.chmodSync(dest, 0o755);
console.log(`copy-shell: ${src} -> ${dest}`);
4 changes: 2 additions & 2 deletions lib/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function Database(filenameGiven, options) {
// Load the native addon
let addon;
if (nativeBinding == null) {
addon = DEFAULT_ADDON || (DEFAULT_ADDON = require('bindings')('better_sqlite3.node'));
addon = DEFAULT_ADDON || (DEFAULT_ADDON = require('node-gyp-build')(path.resolve(__dirname, '..')));
} else if (typeof nativeBinding === 'string') {
// See <https://webpack.js.org/api/module-variables/#__non_webpack_require__-webpack-specific>
const requireFunc = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require;
Expand Down Expand Up @@ -88,7 +88,7 @@ Database.prototype.loadExtension = wrappers.loadExtension;
Database.prototype[util.inspect] = require('./methods/inspect');

// Export SQLITE_SCANSTAT_* constants from native addon
const nativeAddon = DEFAULT_ADDON || require('bindings')('better_sqlite3.node');
const nativeAddon = DEFAULT_ADDON || require('node-gyp-build')(path.resolve(__dirname, '..'));
Database.SQLITE_SCANSTAT_NLOOP = nativeAddon.SQLITE_SCANSTAT_NLOOP;
Database.SQLITE_SCANSTAT_NVISIT = nativeAddon.SQLITE_SCANSTAT_NVISIT;
Database.SQLITE_SCANSTAT_EST = nativeAddon.SQLITE_SCANSTAT_EST;
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"src/**/*.[ch]pp",
"lib/**",
"deps/**",
"prebuilds/**",
"shell.js"
],
"engines": {
Expand All @@ -25,11 +26,10 @@
},
"types": "lib/index.d.ts",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
"node-gyp-build": "^4.8.4"
},
"overrides": {
"prebuild": {
"prebuildify": {
"node-abi": "^4.25.0"
}
},
Expand All @@ -38,13 +38,14 @@
"cli-color": "^2.0.3",
"fs-extra": "^11.1.1",
"mocha": "^11.7.5",
"node-gyp": "^11.0.0",
"nodemark": "^0.3.0",
"prebuild": "^13.0.1",
"prebuildify": "^6.0.1",
"sqlite": "^5.0.1",
"sqlite3": "^5.1.6"
},
"scripts": {
"install": "prebuild-install || node -e \"require('fs').existsSync('build/Release/better_sqlite3.node')||process.exit(1)\" || node-gyp rebuild --release",
"install": "node-gyp-build",
"build-release": "node-gyp rebuild --release",
"build-debug": "node-gyp rebuild --debug",
"test": "mocha --exit --slow=75 --timeout=5000",
Expand Down
16 changes: 13 additions & 3 deletions shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ const path = require('path');
const fs = require('fs');

const binaryName = process.platform === 'win32' ? 'zero_sqlite3.exe' : 'zero_sqlite3';
const binary = path.join(__dirname, 'build', 'Release', binaryName);

if (!fs.existsSync(binary)) {
console.error(`Error: Binary not found at ${binary}`);
// Prefer a locally built shell, then fall back to the prebuilt one shipped in
// the npm package (prebuilds/<platform>-<arch>/). No prebuilt shell exists for
// musl (Alpine) because the zero_sqlite3 target is not built there.
const isMusl = process.platform === 'linux' && fs.existsSync('/etc/alpine-release');
const candidates = [path.join(__dirname, 'build', 'Release', binaryName)];
if (!isMusl) {
candidates.push(path.join(__dirname, 'prebuilds', `${process.platform}-${process.arch}`, binaryName));
}

const binary = candidates.find(fs.existsSync);

if (!binary) {
console.error(`Error: Binary not found at ${candidates.join(' or ')}`);
console.error('Please run: npm run build-release');
process.exit(1);
}
Expand Down
2 changes: 1 addition & 1 deletion test/10.database.open.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('new Database()', function () {
});
it('should accept the "nativeBinding" option', function () {
this.slow(500);
const oldBinding = require('bindings')({ bindings: 'better_sqlite3.node', path: true });
const oldBinding = require('node-gyp-build').path(path.join(__dirname, '..'));
const newBinding = path.join(path.dirname(oldBinding), 'test.node');
expect(oldBinding).to.be.a('string');
fs.copyFileSync(oldBinding, newBinding);
Expand Down
Loading