name: Build Router Native Modules on: push: branches: [main] paths: - 'crates/ruvector-router-core/**' - 'crates/ruvector-router-ffi/**' - 'npm/packages/router/**' - '.github/workflows/build-router.yml' tags: - 'v*' pull_request: branches: [main] paths: - 'crates/ruvector-router-core/**' - 'crates/ruvector-router-ffi/**' - 'npm/packages/router/**' workflow_dispatch: inputs: publish: description: 'Publish to npm after build' required: false type: boolean default: false env: CARGO_TERM_COLOR: always jobs: build: strategy: fail-fast: false matrix: settings: - host: ubuntu-22.04 target: x86_64-unknown-linux-gnu platform: linux-x64-gnu - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu platform: linux-arm64-gnu - host: macos-14 target: x86_64-apple-darwin platform: darwin-x64 - host: macos-14 target: aarch64-apple-darwin platform: darwin-arm64 - host: windows-2022 target: x86_64-pc-windows-msvc platform: win32-x64-msvc name: Build Router ${{ matrix.settings.platform }} runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache Rust uses: Swatinem/rust-cache@v2 with: key: router-${{ matrix.settings.target }} - name: Install cross-compilation tools (Linux ARM64) if: matrix.settings.platform == 'linux-arm64-gnu' run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - name: Install dependencies working-directory: npm/packages/router run: npm install --ignore-scripts --omit=optional --force - name: Build native module working-directory: npm/packages/router run: | npx napi build --platform --release --cargo-cwd ../../../crates/ruvector-router-ffi --target ${{ matrix.settings.target }} env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - name: Find built .node files (debug) shell: bash run: | echo "=== Searching for Router .node files ===" find npm/packages/router -name "*.node" -type f 2>/dev/null || true find crates/ruvector-router-ffi -name "*.node" -type f 2>/dev/null || true - name: Prepare artifact shell: bash run: | mkdir -p router-artifacts/${{ matrix.settings.platform }} # Find the built .node file NODE_FILE=$(find npm/packages/router -name "*.node" -type f | head -1) if [ -z "$NODE_FILE" ]; then NODE_FILE=$(find crates/ruvector-router-ffi -name "*.node" -type f | head -1) fi if [ -z "$NODE_FILE" ]; then echo "ERROR: No .node file found" exit 1 fi echo "Found: $NODE_FILE" cp -v "$NODE_FILE" "router-artifacts/${{ matrix.settings.platform }}/" - name: Test native module (native platform only) if: | (matrix.settings.platform == 'linux-x64-gnu' && runner.os == 'Linux') || (matrix.settings.platform == 'darwin-x64' && runner.os == 'macOS' && runner.arch == 'X64') || (matrix.settings.platform == 'darwin-arm64' && runner.os == 'macOS' && runner.arch == 'ARM64') || (matrix.settings.platform == 'win32-x64-msvc' && runner.os == 'Windows') continue-on-error: true working-directory: npm/packages/router run: npm test - name: Upload artifact uses: actions/upload-artifact@v4 with: name: router-${{ matrix.settings.platform }} path: router-artifacts/${{ matrix.settings.platform }}/*.node if-no-files-found: error publish: name: Publish Router Platform Packages runs-on: ubuntu-22.04 needs: build if: inputs.publish == true || startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' registry-url: 'https://registry.npmjs.org' - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts - name: Create and publish platform packages env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | VERSION=$(node -p "require('./npm/packages/router/package.json').version") echo "Publishing version: $VERSION" for dir in artifacts/router-*/; do platform=$(basename "$dir" | sed 's/router-//') NODE_FILE=$(find "$dir" -name "*.node" | head -1) if [ -z "$NODE_FILE" ]; then echo "No .node file found in $dir" continue fi echo "=== Publishing @ruvector/router-${platform}@${VERSION} ===" # Create package directory PKG_DIR="npm-pkg/router-${platform}" mkdir -p "$PKG_DIR" # Determine OS and CPU case "$platform" in linux-x64-gnu) OS="linux"; CPU="x64"; LIBC='"libc": ["glibc"],' NODE_NAME="ruvector-router.linux-x64-gnu.node" ;; linux-arm64-gnu) OS="linux"; CPU="arm64"; LIBC='"libc": ["glibc"],' NODE_NAME="ruvector-router.linux-arm64-gnu.node" ;; darwin-x64) OS="darwin"; CPU="x64"; LIBC="" NODE_NAME="ruvector-router.darwin-x64.node" ;; darwin-arm64) OS="darwin"; CPU="arm64"; LIBC="" NODE_NAME="ruvector-router.darwin-arm64.node" ;; win32-x64-msvc) OS="win32"; CPU="x64"; LIBC="" NODE_NAME="ruvector-router.win32-x64-msvc.node" ;; esac # Copy and rename binary cp "$NODE_FILE" "$PKG_DIR/$NODE_NAME" # Create package.json cat > "$PKG_DIR/package.json" << EOF { "name": "@ruvector/router-${platform}", "version": "${VERSION}", "os": ["${OS}"], "cpu": ["${CPU}"], ${LIBC} "main": "${NODE_NAME}", "files": ["${NODE_NAME}"], "description": "Semantic router for AI agents - ${platform} platform", "keywords": ["ruvector", "router", "semantic-router", "napi-rs"], "author": "ruv.io Team ", "license": "MIT", "repository": {"type": "git", "url": "https://github.com/ruvnet/ruvector"}, "engines": {"node": ">= 18"}, "publishConfig": {"registry": "https://registry.npmjs.org/", "access": "public"} } EOF # Publish cd "$PKG_DIR" npm publish --access public || echo "Failed to publish @ruvector/router-${platform}" cd ../.. done - name: Copy binaries to main package run: | for dir in artifacts/router-*/; do platform=$(basename "$dir" | sed 's/router-//') NODE_FILE=$(find "$dir" -name "*.node" | head -1) if [ -n "$NODE_FILE" ]; then cp -v "$NODE_FILE" "npm/packages/router/ruvector-router.${platform}.node" fi done ls -la npm/packages/router/*.node || true - name: Publish main package working-directory: npm/packages/router env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm publish --access public || echo "Package may already exist"