Skip to content

v0.5.6

v0.5.6 #26

Workflow file for this run

# Build Node.js SDK for all supported platforms.
#
# Self-contained workflow that builds and publishes Node.js packages to npm.
# Uses napi-rs for native Rust bindings with platform-specific packages.
#
# Package structure:
# - @boxlite-ai/boxlite (main package with TypeScript wrappers)
# - @boxlite-ai/boxlite-darwin-arm64 (macOS ARM64 native binary)
# - @boxlite-ai/boxlite-linux-x64-gnu (Linux x64 glibc native binary)
#
# Triggers:
# - release: When a GitHub release is published
# - workflow_dispatch: Manual trigger for testing
name: Build Node.js
on:
release:
types: [ published ]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
config:
uses: ./.github/workflows/config.yml
# ============================================================================
# BUILD
# ============================================================================
# Builds native Node.js bindings for each platform using napi-rs.
#
# Platform-specific considerations:
# - Linux: Uses manylinux_2_28 container for glibc compatibility
# - Guest binary built on host first (Ubuntu has musl-tools, manylinux doesn't)
# - Shim/libkrun built in container (needs glibc 2.28)
# - macOS: Builds directly on runner (ARM64 only)
#
# Outputs artifacts containing:
# - native/boxlite.js (JS loader)
# - npm/<platform>/boxlite.<platform>.node (native binary)
# - npm/<platform>/runtime/* (boxlite-shim, boxlite-guest, libkrun, etc.)
# ============================================================================
build:
name: Build (${{ matrix.target }})
needs: config
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.config.outputs.platforms) }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ needs.config.outputs.node-build-version }}
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ needs.config.outputs.rust-toolchain }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
shared-key: "boxlite"
save-if: false
# Build guest on host BEFORE manylinux container because:
# - manylinux (AlmaLinux/RHEL) doesn't have musl packages in repos
# - Building musl-cross-make from source takes ~15 minutes
# - Ubuntu host has musl-tools via apt (~30 seconds)
# - Guest is static musl binary, doesn't need manylinux glibc compatibility
# - Shim/libkrun must be built IN container (needs glibc 2.28 for manylinux)
- name: Build guest binary (Linux only)
if: runner.os == 'Linux'
run: |
make setup guest
# Cache only the final binary (11MB), not entire target dir (2.3GB)
# Guest is not rebuilt in container (SKIP_GUEST_BUILD=1), so no incremental compilation needed
GUEST_TARGET=$(scripts/util.sh --target)
mkdir -p ".cache/$GUEST_TARGET/release"
cp "target/$GUEST_TARGET/release/boxlite-guest" ".cache/$GUEST_TARGET/release/"
# Clean up to save disk space
rm -rf target ~/.rustup ~/.cargo
- name: Build Node.js package (Linux)
if: runner.os == 'Linux'
uses: addnab/docker-run-action@v3
with:
image: quay.io/pypa/manylinux_2_28_${{ contains(matrix.target, 'arm64') && 'aarch64' || 'x86_64' }}
options: -v ${{ github.workspace }}:/work -w /work
run: |
set -ex
# Mark directory as safe for git (ownership differs in container)
git config --global --add safe.directory /work
# Restore pre-built guest target directory
GUEST_TARGET=$(scripts/util.sh --target)
if [ -d ".cache/$GUEST_TARGET" ]; then
echo "Restoring guest from .cache/$GUEST_TARGET"
mkdir -p target
cp -a ".cache/$GUEST_TARGET" "target/$GUEST_TARGET"
fi
# Build with SKIP_GUEST_BUILD=1 to use pre-built guest
export SKIP_GUEST_BUILD=1
make setup runtime
# Add cargo to PATH for npm commands (matches Makefile pattern)
# (make installs cargo to ~/.cargo/bin but npm subprocesses don't inherit PATH)
export PATH="$HOME/.cargo/bin:$PATH"
# Build Node.js artifacts (no pack - CI uploads raw artifacts)
cd sdks/node
npm install --silent
npm run build:native -- --release
npm run artifacts
npm run bundle:runtime
- name: Build Node.js package (macOS)
if: runner.os == 'macOS'
run: |
make setup runtime
# Build Node.js artifacts (no pack - CI uploads raw artifacts)
cd sdks/node
npm install --silent
npm run build:native -- --release
npm run artifacts
npm run bundle:runtime
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts-${{ matrix.target }}
path: |
sdks/node/native/boxlite.js
sdks/node/npm/
if-no-files-found: error
retention-days: ${{ needs.config.outputs.artifact-retention-days }}
# ============================================================================
# TEST
# ============================================================================
# Tests the built packages on each platform with multiple Node.js versions.
#
# Key steps:
# - Removes npm/* from git checkout (contains all platforms)
# - Downloads only the matching platform's artifacts
# - Verifies the package can be loaded and exports are accessible
# ============================================================================
test:
name: Test (${{ matrix.platform.target }} / Node ${{ matrix.node-version }})
needs: [ config, build ]
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
platform: ${{ fromJson(needs.config.outputs.platforms) }}
node-version: ${{ fromJson(needs.config.outputs.node-versions) }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# Remove npm/* from git checkout to avoid platform mismatch
# (git contains all platforms, but we only want the one from artifacts)
- name: Clean npm workspaces
run: rm -rf sdks/node/npm
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: artifacts-${{ matrix.platform.target }}
path: sdks/node
- name: Test package
run: |
cd sdks/node
npm install --ignore-scripts
npm run build
node -e "import('./dist/index.js').then(m => console.log('BoxLite loaded:', Object.keys(m)))"
# ============================================================================
# PUBLISH TO NPM
# ============================================================================
# Publishes all packages to npm registry using OIDC Trusted Publishing.
#
# Key considerations:
# - Runs on ubuntu-latest but publishes pre-built platform-specific binaries
# - Install dependencies BEFORE downloading artifacts to avoid EBADPLATFORM
# (if npm/* directories exist during install, npm validates platform restrictions
# even with --workspaces=false, causing errors for darwin-arm64 on Linux)
# - Must publish platform packages BEFORE main package (dependency order)
# - Uses npm provenance for supply chain security
# ============================================================================
publish:
name: Publish to npm
needs: [ config, build, test ]
if: github.event_name == 'release' && github.event.action == 'published'
runs-on: ubuntu-latest
permissions:
id-token: write # Required for npm OIDC Trusted Publishing
contents: read
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
# Install BEFORE downloading artifacts to avoid EBADPLATFORM error.
# If npm/* directories exist, npm validates platform restrictions even
# with --workspaces=false, causing errors for darwin-arm64 on Linux.
- name: Install dependencies
run: cd sdks/node && npm install --ignore-scripts
- name: Build TypeScript
run: cd sdks/node && npm run build
# Download pre-built artifacts from all platforms (darwin-arm64, linux-x64-gnu)
# merge-multiple: true combines artifacts from different matrix jobs into one directory
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: sdks/node
pattern: artifacts-*
merge-multiple: true
- name: List artifacts
run: |
ls -la sdks/node/native/
ls -la sdks/node/npm/
# -------------------------------------------------------------------------
# NPM PUBLISH (Trusted Publishing via OIDC - no NPM_TOKEN needed)
# -------------------------------------------------------------------------
# IMPORTANT: Publish order matters!
# 1. Platform packages first: @boxlite-ai/boxlite-darwin-arm64, etc.
# 2. Main package last: @boxlite-ai/boxlite
#
# The main package has optionalDependencies pointing to platform packages.
# If we publish main first, npm install will fail because the platform
# packages don't exist yet on the registry.
# -------------------------------------------------------------------------
- name: Publish platform packages (workspaces)
run: cd sdks/node && npm publish --provenance --access public --workspaces
- name: Publish main package
run: cd sdks/node && npm publish --provenance --access public
# ============================================================================
# UPLOAD TO GITHUB RELEASE
# ============================================================================
# Creates npm-compatible tarballs and uploads them to the GitHub Release.
#
# This provides an alternative installation method:
# npm install https://github.com/boxlite-ai/boxlite/releases/download/v0.1.5/boxlite-ai-boxlite-0.1.5.tgz
#
# Uses `npm run pack:all` which:
# - Creates properly versioned .tgz files (e.g., boxlite-ai-boxlite-0.1.5.tgz)
# - Includes all platform packages and the main package
# - Produces npm-installable tarballs (unlike raw tar.gz of directories)
# ============================================================================
upload-to-release:
name: Upload to GitHub Release
needs: [ config, build, test ]
if: github.event_name == 'release' && github.event.action == 'published'
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload assets to GitHub Release
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
# Install BEFORE downloading artifacts to avoid EBADPLATFORM error.
# (same reason as publish job - npm validates platform restrictions if npm/* exists)
- name: Install dependencies
run: cd sdks/node && npm install --ignore-scripts
# Download pre-built artifacts from all platforms
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: sdks/node
pattern: artifacts-*
merge-multiple: true
# pack:all creates versioned .tgz files for all packages:
# - boxlite-ai-boxlite-X.Y.Z.tgz (main package)
# - boxlite-ai-boxlite-darwin-arm64-X.Y.Z.tgz
# - boxlite-ai-boxlite-linux-x64-gnu-X.Y.Z.tgz
- name: Build and pack packages
run: |
cd sdks/node
npm run build
npm run pack:all
ls -la packages/
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: sdks/node/packages/*.tgz
fail_on_unmatched_files: true