name: SONA NAPI Build & Publish on: push: tags: - 'sona-v*' paths: - 'crates/sona/**' - 'npm/packages/sona/**' - '.github/workflows/sona-napi.yml' pull_request: paths: - 'crates/sona/**' - 'npm/packages/sona/**' workflow_dispatch: inputs: publish: description: 'Publish to npm' type: boolean default: false env: CARGO_TERM_COLOR: always jobs: build: strategy: fail-fast: false matrix: include: # Linux x64 GNU - os: ubuntu-latest target: x86_64-unknown-linux-gnu node-file: sona.linux-x64-gnu.node # Linux x64 MUSL - os: ubuntu-latest target: x86_64-unknown-linux-musl node-file: sona.linux-x64-musl.node # Linux ARM64 - os: ubuntu-latest target: aarch64-unknown-linux-gnu node-file: sona.linux-arm64-gnu.node # macOS x64 - os: macos-13 target: x86_64-apple-darwin node-file: sona.darwin-x64.node # macOS ARM64 - os: macos-14 target: aarch64-apple-darwin node-file: sona.darwin-arm64.node # Windows x64 - os: windows-latest target: x86_64-pc-windows-msvc node-file: sona.win32-x64-msvc.node # Windows ARM64 - os: windows-latest target: aarch64-pc-windows-msvc node-file: sona.win32-arm64-msvc.node runs-on: ${{ matrix.os }} name: Build ${{ matrix.target }} steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Install musl tools (Linux MUSL) if: matrix.target == 'x86_64-unknown-linux-musl' run: | sudo apt-get update sudo apt-get install -y musl-tools - name: Install napi-rs CLI run: npm install -g @napi-rs/cli - name: Build native module (cross-compile) if: matrix.target == 'aarch64-unknown-linux-gnu' working-directory: npm/packages/sona run: | npx napi build --platform --release -p ruvector-sona --manifest-path ../../../crates/sona/Cargo.toml --output-dir . -F napi --target ${{ matrix.target }} --use-napi-cross - name: Build native module (macOS) if: startsWith(matrix.target, 'x86_64-apple-darwin') || startsWith(matrix.target, 'aarch64-apple-darwin') working-directory: npm/packages/sona env: CARGO_BUILD_TARGET: ${{ matrix.target }} RUSTFLAGS: '-C link-arg=-undefined -C link-arg=dynamic_lookup' run: | npx napi build --platform --release -p ruvector-sona --manifest-path ../../../crates/sona/Cargo.toml --output-dir . -F napi --target ${{ matrix.target }} - name: Build native module (other) if: "!startsWith(matrix.target, 'x86_64-apple-darwin') && !startsWith(matrix.target, 'aarch64-apple-darwin') && matrix.target != 'aarch64-unknown-linux-gnu'" working-directory: npm/packages/sona env: CARGO_BUILD_TARGET: ${{ matrix.target }} run: | npx napi build --platform --release -p ruvector-sona --manifest-path ../../../crates/sona/Cargo.toml --output-dir . -F napi --target ${{ matrix.target }} - name: List built files working-directory: npm/packages/sona shell: bash run: ls -la *.node || echo "No .node files" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.target }} path: npm/packages/sona/${{ matrix.node-file }} if-no-files-found: error # Build universal macOS binary universal-macos: runs-on: macos-14 name: Universal macOS needs: build steps: - uses: actions/checkout@v4 - name: Download x64 artifact uses: actions/download-artifact@v4 with: name: bindings-x86_64-apple-darwin path: artifacts/x64 - name: Download ARM64 artifact uses: actions/download-artifact@v4 with: name: bindings-aarch64-apple-darwin path: artifacts/arm64 - name: Create universal binary run: | mkdir -p artifacts/universal lipo -create \ artifacts/x64/sona.darwin-x64.node \ artifacts/arm64/sona.darwin-arm64.node \ -output artifacts/universal/sona.darwin-universal.node - name: Upload universal artifact uses: actions/upload-artifact@v4 with: name: bindings-darwin-universal path: artifacts/universal/sona.darwin-universal.node # Publish to npm publish: runs-on: ubuntu-latest name: Publish npm packages needs: [build, universal-macos] if: startsWith(github.ref, 'refs/tags/sona-v') || github.event.inputs.publish == 'true' steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - name: Install napi-rs CLI run: npm install -g @napi-rs/cli - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts - name: List artifacts run: | echo "=== All downloaded artifacts ===" find artifacts -name "*.node" -ls - name: Copy .node files to npm package working-directory: npm/packages/sona run: | # Copy all .node files from artifacts to the package directory cp ../../../artifacts/bindings-x86_64-unknown-linux-gnu/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-x86_64-unknown-linux-musl/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-aarch64-unknown-linux-gnu/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-x86_64-apple-darwin/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-aarch64-apple-darwin/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-x86_64-pc-windows-msvc/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-aarch64-pc-windows-msvc/*.node . 2>/dev/null || true cp ../../../artifacts/bindings-darwin-universal/*.node . 2>/dev/null || true echo "=== .node files in package ===" ls -la *.node - name: Create and publish platform packages working-directory: npm/packages/sona env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | VERSION=$(node -p "require('./package.json').version") echo "Publishing version: $VERSION" mkdir -p npm publish_platform() { local name=$1 local node_file=$2 local os_val=$3 local cpu_val=$4 local libc_val=$5 if [ -f "$node_file" ]; then local dir_name=$(echo "$name" | sed 's/@ruvector\/sona-//') mkdir -p "npm/$dir_name" cp "$node_file" "npm/$dir_name/" if [ -n "$libc_val" ]; then node -e "require('fs').writeFileSync('npm/$dir_name/package.json', JSON.stringify({name:'$name',version:'$VERSION',os:['$os_val'],cpu:['$cpu_val'],libc:['$libc_val'],main:'$node_file',files:['$node_file'],license:'MIT OR Apache-2.0',repository:{type:'git',url:'https://github.com/ruvnet/ruvector.git'}},null,2))" else node -e "require('fs').writeFileSync('npm/$dir_name/package.json', JSON.stringify({name:'$name',version:'$VERSION',os:['$os_val'],cpu:['$cpu_val'],main:'$node_file',files:['$node_file'],license:'MIT OR Apache-2.0',repository:{type:'git',url:'https://github.com/ruvnet/ruvector.git'}},null,2))" fi echo "Publishing $name..." cd "npm/$dir_name" npm publish --access public || echo "Warning: $name may already exist" cd ../.. fi } publish_platform "@ruvector/sona-linux-x64-gnu" "sona.linux-x64-gnu.node" "linux" "x64" "" publish_platform "@ruvector/sona-linux-x64-musl" "sona.linux-x64-musl.node" "linux" "x64" "musl" publish_platform "@ruvector/sona-linux-arm64-gnu" "sona.linux-arm64-gnu.node" "linux" "arm64" "" publish_platform "@ruvector/sona-darwin-x64" "sona.darwin-x64.node" "darwin" "x64" "" publish_platform "@ruvector/sona-darwin-arm64" "sona.darwin-arm64.node" "darwin" "arm64" "" publish_platform "@ruvector/sona-win32-x64-msvc" "sona.win32-x64-msvc.node" "win32" "x64" "" publish_platform "@ruvector/sona-win32-arm64-msvc" "sona.win32-arm64-msvc.node" "win32" "arm64" "" echo "=== Platform packages published ===" - name: Update main package with optionalDependencies working-directory: npm/packages/sona run: | VERSION=$(node -p "require('./package.json').version") # Add optionalDependencies to package.json node -e " const pkg = require('./package.json'); pkg.optionalDependencies = { '@ruvector/sona-linux-x64-gnu': '$VERSION', '@ruvector/sona-linux-x64-musl': '$VERSION', '@ruvector/sona-linux-arm64-gnu': '$VERSION', '@ruvector/sona-darwin-x64': '$VERSION', '@ruvector/sona-darwin-arm64': '$VERSION', '@ruvector/sona-win32-x64-msvc': '$VERSION', '@ruvector/sona-win32-arm64-msvc': '$VERSION' }; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); " echo "=== Updated package.json ===" cat package.json - name: Publish main package working-directory: npm/packages/sona env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | echo "=== Main package contents ===" ls -la # Publish main package npm publish --access public # Test installation on all platforms test-install: runs-on: ${{ matrix.os }} name: Test ${{ matrix.os }} needs: publish if: startsWith(github.ref, 'refs/tags/sona-v') strategy: matrix: os: [ubuntu-latest, macos-14, windows-latest] steps: - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Wait for npm propagation run: sleep 30 - name: Test npm install run: | npm init -y npm install @ruvector/sona@latest node -e "const sona = require('@ruvector/sona'); console.log('SONA loaded successfully!'); console.log('SonaEngine:', typeof sona.SonaEngine);"