diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 902e1b9..840f994 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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/-/; + # 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-/ directory. + # --no-napi: better-sqlite3 uses V8 APIs directly, so binaries are per-ABI. + # --name node: emit canonical node-gyp-build names (node.abi[.].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 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: @@ -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: @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/.gitignore b/.gitignore index 729c316..b12c5b2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ coverage # Compiled binary addons (http://nodejs.org/api/addons.html) build/ +prebuilds/ # Dependency directory node_modules diff --git a/deps/copy-shell.js b/deps/copy-shell.js new file mode 100644 index 0000000..be4f30d --- /dev/null +++ b/deps/copy-shell.js @@ -0,0 +1,35 @@ +'use strict'; +// Copies the zero_sqlite3 shell executable produced by node-gyp into the +// prebuilds/-/ 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 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}`); diff --git a/lib/database.js b/lib/database.js index f1e269e..c46fef8 100644 --- a/lib/database.js +++ b/lib/database.js @@ -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 const requireFunc = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require; @@ -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; diff --git a/package.json b/package.json index 37acb78..9be961c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "src/**/*.[ch]pp", "lib/**", "deps/**", + "prebuilds/**", "shell.js" ], "engines": { @@ -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" } }, @@ -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", diff --git a/shell.js b/shell.js index c9632c3..519b6da 100755 --- a/shell.js +++ b/shell.js @@ -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/-/). 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); } diff --git a/test/10.database.open.js b/test/10.database.open.js index 667c9cb..21744f2 100644 --- a/test/10.database.open.js +++ b/test/10.database.open.js @@ -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);