diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79ca4b08..36967eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,22 @@ on: - '*' permissions: contents: read + env: FORCE_COLOR: 1 + PREBUILD_NODE_VERSION: '20' + DEFAULT_NODE_VERSION: '24' + ALPINE_VARIANT: 'alpine3.20' + jobs: + env_vars: + runs-on: ubuntu-latest + outputs: + prebuild_node_version: ${{ env.PREBUILD_NODE_VERSION }} + default_node_version: ${{ env.DEFAULT_NODE_VERSION }} + steps: + - run: echo "exposing environment variables for passing to reusable workflows" + verify-version: runs-on: ubuntu-latest steps: @@ -65,7 +78,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: ${{ env.DEFAULT_NODE_VERSION }} - name: Install dependencies run: yarn install --frozen-lockfile --ignore-scripts - name: Run lint @@ -88,11 +101,6 @@ jobs: target: x64 platform: macos-x64 node: 20 - - os: macos-latest - host: x64 - target: x64 - platform: macos-x64 - node: 22 - os: macos-latest host: x64 target: x64 @@ -103,11 +111,6 @@ jobs: target: x64 platform: linux-x64 node: 20 - - os: ubuntu-24.04 - host: x64 - target: x64 - platform: linux-x64 - node: 22 - os: ubuntu-24.04 host: x64 target: x64 @@ -118,26 +121,26 @@ jobs: target: x64 platform: win32-x64 node: 20 - - os: windows-latest - host: x64 - target: x64 - platform: win32-x64 - node: 22 - os: windows-latest host: x64 target: x64 platform: win32-x64 node: 24 - - os: macos-latest + - os: windows-11-arm host: arm64 target: arm64 - platform: macos-arm64 + platform: win32-arm64 node: 20 + - os: windows-11-arm + host: arm64 + target: arm64 + platform: win32-arm64 + node: 24 - os: macos-latest host: arm64 target: arm64 platform: macos-arm64 - node: 22 + node: 20 - os: macos-latest host: arm64 target: arm64 @@ -148,11 +151,6 @@ jobs: target: arm64 platform: linux-arm64 node: 20 - - os: ubuntu-24.04-arm - host: arm64 - target: arm64 - platform: linux-arm64 - node: 22 - os: ubuntu-24.04-arm host: arm64 target: arm64 @@ -219,7 +217,7 @@ jobs: - name: Upload binaries to commit artifacts uses: actions/upload-artifact@v7 - if: matrix.node == 24 + if: matrix.node == ${{ env.PREBUILD_NODE_VERSION }} with: name: prebuilt-binaries-${{ matrix.platform }} path: prebuilds/* @@ -238,13 +236,13 @@ jobs: sleep 10 fi done - if: matrix.node == 24 && startsWith(github.ref, 'refs/tags/') + if: matrix.node == ${{ env.PREBUILD_NODE_VERSION }} && startsWith(github.ref, 'refs/tags/') env: GH_TOKEN: ${{ github.token }} - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 - if: matrix.node == 24 && matrix.platform == 'linux-x64' + if: matrix.node == ${{ env.DEFAULT_NODE_VERSION }} && matrix.platform == 'linux-x64' with: token: ${{ secrets.CODECOV_TOKEN }} slug: gms1/node-sqlite3 @@ -278,8 +276,8 @@ jobs: --file ./tools/BinaryBuilder.Dockerfile \ --tag sqlite-builder \ --no-cache \ - --build-arg VARIANT=alpine3.20 \ - --build-arg NODE_VERSION=24 \ + --build-arg VARIANT=${{ env.ALPINE_VARIANT }} \ + --build-arg NODE_VERSION=${{ env.PREBUILD_NODE_VERSION }} \ . CONTAINER_ID=$(docker create -it sqlite-builder) docker cp $CONTAINER_ID:/usr/src/build/prebuilds/ ./prebuilds @@ -321,7 +319,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: ${{ env.PREBUILD_NODE_VERSION }} - name: Install dependencies run: yarn install --frozen-lockfile --ignore-scripts @@ -359,11 +357,11 @@ jobs: GH_TOKEN: ${{ github.token }} test-package: - needs: [package] + needs: [package, env_vars] if: >- !cancelled() && needs.package.result == 'success' uses: ./.github/workflows/test-npm-package.yml with: target_run_id: ${{ github.run_id }} - node_version: '22' + node_version: ${{ needs.env_vars.outputs.default_node_version }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 54757b05..6e68274b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,7 @@ permissions: env: FORCE_COLOR: 1 + DEFAULT_NODE_VERSION: '24' jobs: publish: @@ -36,7 +37,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: ${{ env.DEFAULT_NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Download tarball from GitHub Release diff --git a/.github/workflows/test-npm-package.yml b/.github/workflows/test-npm-package.yml index 135d3172..5278f86b 100644 --- a/.github/workflows/test-npm-package.yml +++ b/.github/workflows/test-npm-package.yml @@ -40,6 +40,8 @@ jobs: platform: macos-arm64 - os: windows-latest platform: win32-x64 + - os: windows-11-arm + platform: win32-arm64 runs-on: ${{ matrix.os }} name: ${{ matrix.platform }} steps: @@ -162,4 +164,4 @@ jobs: } main().catch(err => { console.error(err); process.exit(1); }); EOF - node test-promise.mjs \ No newline at end of file + node test-promise.mjs diff --git a/README.md b/README.md index 5867b0e7..95444c91 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Prebuilt binaries are bundled inside the npm package using [`prebuildify`](https * `linux-arm64` (musl) * `linux-x64` (musl) * `win32-x64` +* `win32-arm64` Support for other platforms and architectures may be added in the future if CI supports building on them. diff --git a/memory-bank/build-system.md b/memory-bank/build-system.md index aa235ac2..f0ccd675 100644 --- a/memory-bank/build-system.md +++ b/memory-bank/build-system.md @@ -125,11 +125,11 @@ The `NAPI_VERSION` define is set via `napi_build_version` variable in binding.gy **How it works**: - The `napi_build_version` variable is automatically set by node-gyp based on the target Node.js version - For local builds, it's stored in `build/config.gypi` (e.g., `"napi_build_version": "9"`) -- For prebuilds, `prebuildify` passes it via the `--napi` flag which builds a single NAPI-version-agnostic binary (named `@homeofthings+sqlite3..node`). The actual NAPI version used at compile time is determined by the Node.js version running the build (e.g., Node 24 supports NAPI v9). Since NAPI is backward compatible, a binary built with NAPI v9 runs on any Node.js supporting v9 or lower. +- For prebuilds, `prebuildify` passes it via the `--napi` flag which builds a single NAPI-version-agnostic binary (named `@homeofthings+sqlite3..node`). The actual NAPI version used at compile time is determined by the Node.js version running the build. Since NAPI is forward-compatible, prebuilts must be built on the lowest supported Node version to maximize compatibility — building on Node 20 (NAPI v9) produces prebuilts compatible with all Node.js 20+ versions. ### NAPI Versions Configuration -With `prebuildify --napi`, the NAPI version is auto-detected from the build Node.js version — it is not explicitly configured. The CI builds prebuilds on Node 24 (which supports NAPI v9), producing a single `@homeofthings+sqlite3.glibc.node` binary per platform. The historical `[3, 6]` configuration from the old `binary.napi_versions` package.json field is no longer used. +With `prebuildify --napi`, the NAPI version is auto-detected from the build Node.js version — it is not explicitly configured. The CI builds prebuilds on Node 20 (which supports NAPI v9), producing a single `@homeofthings+sqlite3.glibc.node` binary per platform. The `PREBUILD_NODE_VERSION` workflow variable controls which Node version is used for prebuilds. **Why multiple versions?** @@ -146,18 +146,14 @@ NAPI versions are independent of Node.js versions - they represent API feature t | v9 | External strings, syntax error creation | | v10 | Latin1 external strings | -**Backward Compatibility**: +**Forward Compatibility**: -NAPI is backward compatible - a binary built for NAPI v3 will run on any Node.js that supports v3 or higher. Since Node.js 20.17.0+ supports NAPI v9, it can run binaries built for v3, v6, or v9. +NAPI is forward-compatible — a binary built with NAPI_VERSION=X requires a Node.js version supporting NAPI vX or higher. Since our prebuilts are built with NAPI v9 on Node 20, they are compatible with all Node.js 20+ versions (which support NAPI v9 or higher). **Code Conditionals**: The source code uses `#if NAPI_VERSION < 6` conditionals in [`src/database.h`](../src/database.h) and [`src/database.cc`](../src/database.cc) to provide backward compatibility for NAPI versions below v6. When building for NAPI v6+, these conditionals are disabled. -**Current Configuration Rationale**: - -The `[3, 6]` configuration is historical from when this fork supported older Node.js versions. Since the project now requires Node.js >= 20.17.0 (which supports NAPI v9), both prebuilt variants work correctly. Future versions could simplify to a single NAPI version (e.g., v6 or v9). - ## Assert Control ### Asserts in Debug Mode @@ -230,7 +226,7 @@ The `install` script runs `node-gyp-build` which tests whether the prebuilt bina ## Platform Support - Node.js >= 20.17.0 -- NAPI: version-agnostic (`@homeofthings+sqlite3.*.node`), built with NAPI v9 on Node 24 +- NAPI: version-agnostic (`@homeofthings+sqlite3.*.node`), built with NAPI v9 on Node 20 (PREBUILD_NODE_VERSION) - Platforms: Linux (glibc + musl), macOS, Windows (see CI configuration) ## Security Hardening @@ -317,23 +313,34 @@ The project uses three GitHub Actions workflows for continuous integration and r **Triggers**: `workflow_dispatch`, `pull_request`, push to `main`, tags (`*`) +**Workflow Environment Variables**: + +| Variable | Value | Purpose | +|----------|-------|---------| +| `PREBUILD_NODE_VERSION` | `'20'` | Node version for building prebuilts (NAPI v9, compatible with all Node 20+) | +| `DEFAULT_NODE_VERSION` | `'24'` | Node version for lint, Codecov, smoke tests, packaging | +| `ALPINE_VARIANT` | `'alpine3.20'` | Alpine variant for musl Docker builds | + **Jobs**: -1. **verify-version** — On tag events, checks that the tag version matches `package.json` version. Fails the build if they don't match. +1. **env_vars** — Exposes workflow env variables as job outputs for use in reusable workflow calls (`needs.env_vars.outputs.*`). This is needed because the `env` context is not available in `with:` blocks of reusable workflows. + +2. **verify-version** — On tag events, checks that the tag version matches `package.json` version. Fails the build if they don't match. -2. **create-release** — On tag events, creates a draft GitHub Release using `gh release create --draft`. This ensures the release exists before `build` and `build-musl` jobs try to upload binaries. Skipped for non-tag events (PRs, pushes to main). +3. **create-release** — On tag events, creates a draft GitHub Release using `gh release create --draft`. This ensures the release exists before `build` and `build-musl` jobs try to upload binaries. Skipped for non-tag events (PRs, pushes to main). -3. **lint** — Runs `yarn lint` on `ubuntu-latest` with Node 24. +4. **lint** — Runs `yarn lint` on `ubuntu-latest` with `DEFAULT_NODE_VERSION`. -4. **build** — Builds and tests native binaries across a 14-target matrix. Depends on `[verify-version, lint, create-release]`. +5. **build** — Builds and tests native binaries across a 12-target matrix. Depends on `[verify-version, lint, create-release]`. | OS | Host | Target | Platform | Node Versions | |----|------|--------|----------|---------------| - | macos-latest | x64 | x64 | macos-x64 | 20, 22, 24 | - | macos-latest | arm64 | arm64 | macos-arm64 | 20, 22, 24 | - | ubuntu-24.04 | x64 | x64 | linux-x64 | 20, 22, 24 | - | ubuntu-24.04-arm | arm64 | arm64 | linux-arm64 | 20, 22, 24 | - | windows-latest | x64 | x64 | win32-x64 | 20, 22, 24 | + | macos-latest | x64 | x64 | macos-x64 | 20, 24 | + | macos-latest | arm64 | arm64 | macos-arm64 | 20, 24 | + | ubuntu-24.04 | x64 | x64 | linux-x64 | 20, 24 | + | ubuntu-24.04-arm | arm64 | arm64 | linux-arm64 | 20, 24 | + | windows-latest | x64 | x64 | win32-x64 | 20, 24 | + | windows-11-arm | arm64 | arm64 | win32-arm64 | 20, 24 | Steps per matrix entry: - Install dependencies (`yarn install --frozen-lockfile --ignore-scripts`) @@ -343,15 +350,15 @@ The project uses three GitHub Actions workflows for continuous integration and r - Print binary info (Linux: `ldd`, `nm`, `file`) - **Debug async hook stack integrity** (macOS only): Runs `async_hooks_stress.test.js` with `SQLITE3_DEBUG_ASYNC_HOOKS=1` - Run tests (`yarn test`) - - Upload binaries as commit artifacts (Node 24 only, 7-day retention) - - Upload binaries to GitHub Release (Node 24 + tag events only, uses `prebuilds/*/*.node` glob to avoid matching directories) - - Upload coverage to Codecov (linux-x64 + Node 24 only) + - Upload binaries as commit artifacts (`PREBUILD_NODE_VERSION` only, 7-day retention) + - Upload binaries to GitHub Release (`PREBUILD_NODE_VERSION` + tag events only, uses `prebuilds/*/*.node` glob to avoid matching directories) + - Upload coverage to Codecov (linux-x64 + `DEFAULT_NODE_VERSION` only) -5. **build-musl** — Builds Linux musl binaries using Docker (`tools/BinaryBuilder.Dockerfile` with Alpine 3.20). Only runs on tag events or `workflow_dispatch`. Depends on `[verify-version, create-release]`. Two targets: `linux/amd64` and `linux/arm64`. +6. **build-musl** — Builds Linux musl binaries using Docker (`tools/BinaryBuilder.Dockerfile` with `ALPINE_VARIANT`). Only runs on tag events or `workflow_dispatch`. Depends on `[verify-version, create-release]`. Two targets: `linux/amd64` and `linux/arm64`. -6. **package** — Merges all prebuilt binary artifacts, creates npm tarball (`npm pack`), uploads tarball as artifact and to GitHub Release. Runs after `build` and `build-musl` succeed. +7. **package** — Merges all prebuilt binary artifacts, creates npm tarball (`npm pack`), uploads tarball as artifact and to GitHub Release. Runs after `build` and `build-musl` succeed. -7. **test-package** — Calls `.github/workflows/test-npm-package.yml` as a reusable workflow to smoke-test the npm tarball on 4 platforms (linux-x64, linux-arm64, macos-arm64, win32-x64) with Node 22. +8. **test-package** — Calls `.github/workflows/test-npm-package.yml` as a reusable workflow to smoke-test the npm tarball on 4 platforms (linux-x64, linux-arm64, macos-arm64, win32-x64) with `DEFAULT_NODE_VERSION`. Depends on `[package, env_vars]` to pass the node version via `needs.env_vars.outputs.default_node_version`. ### Publish Workflow (`.github/workflows/publish.yml`) @@ -406,4 +413,4 @@ The project uses three GitHub Actions workflows for continuous integration and r - [Project Overview](project-overview.md) - Architecture and components - [Development Workflow](development.md) - Testing and contributing -- [Decision Log](decisionLog.md) - Technical decisions including hardening rationale \ No newline at end of file +- [Decision Log](decisionLog.md) - Technical decisions including hardening rationale diff --git a/memory-bank/decisionLog.md b/memory-bank/decisionLog.md index cb37f5d0..c2151f4a 100644 --- a/memory-bank/decisionLog.md +++ b/memory-bank/decisionLog.md @@ -7,7 +7,7 @@ **Decision**: Use three GitHub Actions workflows for CI/CD: `ci.yml` (build, test, package), `publish.yml` (npm publish), and `test-npm-package.yml` (reusable smoke tests). **Rationale**: -- `ci.yml` handles the full build matrix (14 targets across 5 OS/arch combos x 3 Node versions) plus musl builds, packaging, and smoke testing +- `ci.yml` handles the full build matrix (12 targets across 6 OS/arch combos x 2 Node versions) plus musl builds, packaging, and smoke testing - `test-npm-package.yml` is a reusable workflow (`workflow_call`) to avoid duplicating smoke test logic between CI and manual dispatch - `publish.yml` is a separate manual workflow for npm publishing, using OIDC/trusted publishing for security - The `verify-version` job ensures tag versions match `package.json` before proceeding with releases @@ -15,9 +15,9 @@ - Code coverage is uploaded to Codecov from the linux-x64/Node-24 matrix entry only **Key workflow features**: -- 14-target build matrix (macOS x64/arm64, Linux x64/arm64, Windows x64 x Node 20/22/24) +- 12-target build matrix (macOS x64/arm64, Linux x64/arm64, Windows x64/arm64 x Node 20/24) - Docker-based musl builds using `tools/BinaryBuilder.Dockerfile` with Alpine 3.20 -- Prebuilt binaries uploaded to GitHub Release on tag events (Node 24 only) +- Prebuilt binaries uploaded to GitHub Release on tag events - npm tarball created via `npm pack` and smoke-tested on 4 platforms - ESM smoke tests verify both default and named imports, plus promise API - Publish workflow uses OIDC/trusted publishing (no npm token stored in secrets) @@ -351,4 +351,4 @@ if (locked && pending == before_pending) { **Rationale**: - Established test suite - Handles async operations well -- Compatible with existing tests \ No newline at end of file +- Compatible with existing tests diff --git a/memory-bank/development.md b/memory-bank/development.md index 424a778a..1156e93b 100644 --- a/memory-bank/development.md +++ b/memory-bank/development.md @@ -371,7 +371,7 @@ On every PR and push to `main`, the CI workflow: 1. **Verifies version** — Tag version must match `package.json` version 2. **Lints** — `yarn lint` on ubuntu-latest with Node 24 -3. **Builds & tests** — 14-target matrix across 5 OS/arch combos x 3 Node versions (20, 22, 24) +3. **Builds & tests** — 12-target matrix across 6 OS/arch combos x 2 Node versions (20, 24) 4. **Builds musl binaries** — Docker-based Alpine builds for Linux musl (tag events only) 5. **Packages** — Merges all prebuilt binaries, creates npm tarball 6. **Smoke-tests** — Tests the npm tarball on 4 platforms with ESM + CJS + Promise API tests diff --git a/tools/BinaryBuilder.Dockerfile b/tools/BinaryBuilder.Dockerfile index e9c09b40..b90b1b55 100644 --- a/tools/BinaryBuilder.Dockerfile +++ b/tools/BinaryBuilder.Dockerfile @@ -1,4 +1,4 @@ -ARG NODE_VERSION=24 +ARG NODE_VERSION=20 ARG VARIANT=bookworm FROM node:$NODE_VERSION-$VARIANT