Skip to content

fix(ci): PR feedback #28

fix(ci): PR feedback

fix(ci): PR feedback #28

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
skip_publish:
description: 'Skip npm publish (for testing)'
type: boolean
default: true
jobs:
build-and-test:
name: Build & Test ${{ matrix.package }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# macOS
- os: macos-14
arch: arm64
gpu: metal
package: darwin-arm64
- os: macos-15-intel
arch: x64
gpu: cpu
package: darwin-x64
# Linux x64
- os: ubuntu-22.04
arch: x64
gpu: cpu
package: linux-x64
- os: ubuntu-22.04
arch: x64
gpu: cuda
package: linux-x64-cuda
- os: ubuntu-22.04
arch: x64
gpu: vulkan
package: linux-x64-vulkan
# Linux ARM64 (native runners)
- os: ubuntu-22.04-arm
arch: arm64
gpu: cpu
package: linux-arm64
- os: ubuntu-22.04-arm
arch: arm64
gpu: cuda
package: linux-arm64-cuda
- os: ubuntu-22.04-arm
arch: arm64
gpu: vulkan
package: linux-arm64-vulkan
# Windows x64
- os: windows-2022
arch: x64
gpu: cpu
package: win32-x64
- os: windows-2022
arch: x64
gpu: cuda
package: win32-x64-cuda
- os: windows-2022
arch: x64
gpu: vulkan
package: win32-x64-vulkan
# Windows ARM64 (cross-compiled from x64)
- os: windows-2022
arch: arm64
gpu: cpu
package: win32-arm64
cross_compile: true
- os: windows-2022
arch: arm64
gpu: vulkan
package: win32-arm64-vulkan
cross_compile: true
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Validate llama.cpp version
run: node scripts/sync-llama-cpp.js --check
shell: bash
# Platform-specific build dependencies
- name: Install build tools (Linux x64)
if: runner.os == 'Linux' && matrix.arch == 'x64' && matrix.gpu == 'cpu'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Install build tools (Linux ARM64)
if: runner.os == 'Linux' && matrix.arch == 'arm64'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Provision CUDA toolkit (Linux)
if: matrix.gpu == 'cuda' && runner.os == 'Linux'
uses: ./.github/actions/provision-cuda
with:
version: '12.6.2'
arch: ${{ matrix.arch }}
- name: Install Vulkan SDK (Linux)
if: matrix.gpu == 'vulkan' && runner.os == 'Linux'
uses: jakoch/install-vulkan-sdk-action@v1.2.4
with:
vulkan_version: '1.4.313.0'
install_runtime: true
optional_components: com.lunarg.vulkan.arm64
cache: true
stripdown: true
- name: Provision CUDA toolkit (Windows)
if: matrix.gpu == 'cuda' && runner.os == 'Windows'
uses: ./.github/actions/provision-cuda
with:
version: '12.6.2'
arch: ${{ matrix.arch }}
- name: Install Vulkan SDK (Windows)
if: matrix.gpu == 'vulkan' && runner.os == 'Windows'
uses: jakoch/install-vulkan-sdk-action@v1.2.4
with:
vulkan_version: '1.4.313.0'
install_runtime: true
cache: true
# Note: Do NOT use stripdown on Windows - it removes vulkan-1.dll runtime
- name: Setup LLVM and Ninja for Windows ARM64 cross-compilation
if: runner.os == 'Windows' && matrix.cross_compile == true
shell: pwsh
run: |
choco install llvm ninja -y
echo "CC=clang-cl" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "CXX=clang-cl" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "CMAKE_GENERATOR=Ninja" | Out-File -FilePath $env:GITHUB_ENV -Append
# Build
- name: Install npm dependencies
run: npm install --ignore-scripts
- name: Build native module (Native builds)
if: matrix.cross_compile != true
run: npm run build
env:
LLOYAL_GPU: ${{ matrix.gpu }}
- name: Build native module (Windows ARM64 cross-compile)
if: runner.os == 'Windows' && matrix.cross_compile == true
shell: pwsh
run: |
$env:CMAKE_GENERATOR = "Ninja"
$env:CMAKE_TOOLCHAIN_FILE = "${{ github.workspace }}/cmake/arm64-cross.cmake"
npm run build
env:
LLOYAL_GPU: ${{ matrix.gpu }}
ARCH: arm64
# Create platform package
- name: Create platform package
shell: bash
run: |
node scripts/create-platform-package.js "${{ matrix.package }}" "${{ matrix.os }}" "${{ matrix.arch }}"
# Test platform package (skip for cross-compiled packages - can't run ARM64 on x64)
- name: Cache test models
if: matrix.cross_compile != true
uses: actions/cache@v4
with:
path: models/
key: test-models-v1
- name: Download test models
if: matrix.cross_compile != true
run: bash scripts/download-test-models.sh
- name: Install platform package locally
if: matrix.cross_compile != true
shell: bash
run: |
npm install ./packages/${{ matrix.package }}
rm -rf build/Release
echo "Installed package contents:"
ls -la node_modules/@lloyal-labs/lloyal.node-${{ matrix.package }}/bin/ || echo "Package not found in node_modules"
- name: Verify platform package loading (Unix)
if: runner.os != 'Windows' && matrix.cross_compile != true && matrix.gpu != 'cuda'
shell: bash
run: |
# Set LD_LIBRARY_PATH to find sibling .so files (Linux CUDA/Vulkan)
PKG_BIN="$PWD/node_modules/@lloyal-labs/lloyal.node-${{ matrix.package }}/bin"
export LD_LIBRARY_PATH="${PKG_BIN}:${LD_LIBRARY_PATH:-}"
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
node -e "
const { loadBinary } = require('./lib');
const addon = loadBinary();
console.log('✓ Platform package loaded successfully');
console.log(' Exports:', Object.keys(addon));
"
env:
LLOYAL_GPU: ${{ matrix.gpu }}
LLOYAL_NO_FALLBACK: '1'
- name: Verify platform package loading (Windows)
if: runner.os == 'Windows' && matrix.cross_compile != true && matrix.gpu != 'cuda'
shell: pwsh
run: |
# Add package bin to PATH for DLL discovery
$pkgBin = "$PWD\node_modules\@lloyal-labs\lloyal.node-${{ matrix.package }}\bin"
$env:PATH = "$pkgBin;$env:PATH"
# Add Vulkan SDK runtime to PATH if available
if ($env:VULKAN_SDK) {
$env:PATH = "$env:VULKAN_SDK\Bin;$env:PATH"
# Vulkan runtime is in runtime\x64, not Bin
if (Test-Path "$env:VULKAN_SDK\runtime\x64\vulkan-1.dll") {
$env:PATH = "$env:VULKAN_SDK\runtime\x64;$env:PATH"
Write-Host "Added Vulkan runtime to PATH: $env:VULKAN_SDK\runtime\x64"
}
}
# Add CUDA runtime to PATH if available
if ($env:CUDA_PATH) {
$env:PATH = "$env:CUDA_PATH\bin;$env:PATH"
Write-Host "Added CUDA bin to PATH: $env:CUDA_PATH\bin"
}
Write-Host "Package bin: $pkgBin"
Write-Host "VULKAN_SDK: $env:VULKAN_SDK"
Write-Host "CUDA_PATH: $env:CUDA_PATH"
node -e "
const { loadBinary } = require('./lib');
const addon = loadBinary();
console.log('✓ Platform package loaded successfully');
console.log(' Exports:', Object.keys(addon));
"
env:
LLOYAL_GPU: ${{ matrix.gpu }}
LLOYAL_NO_FALLBACK: '1'
# Integration tests only run where we have real GPU hardware
# Currently: CPU (all platforms) and Metal (macOS)
# TODO: Re-enable vulkan/cuda when self-hosted GPU runners are available
- name: Run integration tests (Windows)
if: runner.os == 'Windows' && matrix.gpu == 'cpu' && matrix.cross_compile != true
shell: pwsh
run: |
$pkgBin = "$PWD\node_modules\@lloyal-labs\lloyal.node-${{ matrix.package }}\bin"
$env:PATH = "$pkgBin;$env:PATH"
if ($env:VULKAN_SDK) {
$env:PATH = "$env:VULKAN_SDK\Bin;$env:PATH"
if (Test-Path "$env:VULKAN_SDK\runtime\x64\vulkan-1.dll") {
$env:PATH = "$env:VULKAN_SDK\runtime\x64;$env:PATH"
}
}
if ($env:CUDA_PATH) { $env:PATH = "$env:CUDA_PATH\bin;$env:PATH" }
npm run test:integration
timeout-minutes: 5
env:
LLOYAL_GPU: ${{ matrix.gpu }}
LLOYAL_NO_FALLBACK: '1'
- name: Run integration tests (Unix)
if: runner.os != 'Windows' && (matrix.gpu == 'cpu' || matrix.gpu == 'metal')
run: |
PKG_BIN="$PWD/node_modules/@lloyal-labs/lloyal.node-${{ matrix.package }}/bin"
export LD_LIBRARY_PATH="${PKG_BIN}:${LD_LIBRARY_PATH:-}"
npm run test:integration
timeout-minutes: 5
env:
LLOYAL_GPU: ${{ matrix.gpu }}
LLOYAL_NO_FALLBACK: '1'
# Upload package artifact for publish job
- name: Upload platform package artifact
uses: actions/upload-artifact@v4
with:
name: package-${{ matrix.package }}
path: packages/${{ matrix.package }}/
retention-days: 1
# GPU Integration Tests via Cloud Run
# Runs real GPU tests on NVIDIA L4 for CUDA/Vulkan packages
gpu-integration:
name: GPU Tests (${{ matrix.backend }})
needs: build-and-test
runs-on: ubuntu-latest
if: success()
permissions:
contents: read
id-token: write # Required for Workload Identity Federation
strategy:
fail-fast: false
matrix:
include:
- backend: cuda
package: linux-x64-cuda
- backend: vulkan
package: linux-x64-vulkan
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_SA_EMAIL }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
# Download built packages from build-and-test job
- name: Download package artifact
uses: actions/download-artifact@v4
with:
name: package-${{ matrix.package }}
path: packages/package-${{ matrix.package }}
# Build and push Docker image
- name: Build GPU test image
run: |
IMAGE="us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/lloyal-ci/gpu-tests:${{ github.sha }}-${{ matrix.backend }}"
docker build \
-f ci/Dockerfile.gpu-tests \
-t "$IMAGE" .
docker push "$IMAGE"
echo "IMAGE=$IMAGE" >> $GITHUB_ENV
# Create/update Cloud Run Job
- name: Deploy Cloud Run Job
run: |
JOB_NAME="lloyal-gpu-test-${{ matrix.backend }}"
# Check if job exists
if gcloud run jobs describe $JOB_NAME --region=us-central1 2>/dev/null; then
gcloud run jobs update $JOB_NAME \
--region=us-central1 \
--image="${IMAGE}" \
--service-account="${{ secrets.GCP_SA_EMAIL }}" \
--set-env-vars=LLOYAL_GPU=${{ matrix.backend }},LLOYAL_NO_FALLBACK=1 \
--task-timeout=10m
else
gcloud run jobs create $JOB_NAME \
--region=us-central1 \
--image="${IMAGE}" \
--service-account="${{ secrets.GCP_SA_EMAIL }}" \
--set-env-vars=LLOYAL_GPU=${{ matrix.backend }},LLOYAL_NO_FALLBACK=1 \
--task-timeout=10m \
--gpu=1 \
--gpu-type=nvidia-l4 \
--memory=8Gi \
--cpu=2 \
--max-retries=0
fi
# Execute the job and wait for completion
- name: Run GPU tests
run: |
JOB_NAME="lloyal-gpu-test-${{ matrix.backend }}"
# Execute job
EXECUTION=$(gcloud run jobs execute $JOB_NAME \
--region=us-central1 \
--wait \
--format='value(metadata.name)')
echo "Execution: $EXECUTION"
# Wait for logs to flush to Cloud Logging
sleep 5
# Get logs
gcloud logging read \
"resource.type=cloud_run_job AND resource.labels.job_name=$JOB_NAME AND resource.labels.location=us-central1" \
--limit=200 \
--format='value(textPayload)'
publish:
name: Publish all packages
needs: [build-and-test, gpu-integration]
runs-on: ubuntu-latest
# Only run if ALL jobs succeeded AND not a test tag AND not manual skip
# Use v0.0.0-gpu-test or workflow_dispatch with skip_publish to test CI
if: success() && !contains(github.ref_name, '-test') && github.event.inputs.skip_publish != true
permissions:
contents: read
id-token: write # Required for npm provenance
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
# Download all platform package artifacts
- name: Download all platform packages
uses: actions/download-artifact@v4
with:
path: packages/
pattern: package-*
merge-multiple: false
- name: List downloaded packages
run: |
echo "Downloaded platform packages:"
ls -la packages/
for dir in packages/package-*/; do
echo " - $(basename $dir): $(ls $dir)"
done
# Publish platform packages
- name: Publish platform packages
shell: bash
run: |
VERSION=$(node -p "require('./package.json').version")
echo "Publishing version: $VERSION"
# Determine npm tag based on version
if [[ "$VERSION" == *-alpha* ]]; then
NPM_TAG="alpha"
elif [[ "$VERSION" == *-beta* ]]; then
NPM_TAG="beta"
elif [[ "$VERSION" == *-rc* ]]; then
NPM_TAG="rc"
else
NPM_TAG="latest"
fi
echo "Using npm tag: $NPM_TAG"
# Publish each platform package
for pkg_dir in packages/package-*/; do
pkg_name=$(basename "$pkg_dir" | sed 's/^package-//')
echo ""
echo "Publishing @lloyal-labs/lloyal.node-${pkg_name}..."
cd "$pkg_dir"
npm publish --access public --tag "$NPM_TAG" --provenance
cd - > /dev/null
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Publish main package
- name: Sync package versions
run: node scripts/sync-versions.js
- name: Publish main package
shell: bash
run: |
VERSION=$(node -p "require('./package.json').version")
if [[ "$VERSION" == *-alpha* ]]; then
npm publish --tag alpha --access public --provenance
elif [[ "$VERSION" == *-beta* ]]; then
npm publish --tag beta --access public --provenance
elif [[ "$VERSION" == *-rc* ]]; then
npm publish --tag rc --access public --provenance
else
npm publish --access public --provenance
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}