mq-bridge 0.3.3

An asynchronous message bridging library connecting Kafka, MQTT, AMQP, NATS, MongoDB, HTTP, and more.
Documentation
name: Publish Node Package

# Publishes the napi-rs addon as the standard layout: a thin root package
# `mq-bridge` plus one binary package per platform
# (`mq-bridge-darwin-arm64`, `mq-bridge-linux-x64-gnu`, ...). The root lists the
# platform packages as optionalDependencies, so each consumer downloads only its
# own ~20MB binary; the napi loader (native.js) requires the matching one.
#
# Linux is built inside a Debian 11 (glibc 2.31) container so the binaries run on
# RHEL 9 (glibc 2.34), Debian 11/12 and Ubuntu 20.04+ — covering the priority
# server targets. macOS/Windows build natively.
#
# Auth: OIDC trusted publishing. The one-time bootstrap (which used an NPM_TOKEN
# secret to create the 5 platform packages, since npm has no pending-publisher
# concept) is done; all six packages exist with trusted publishing configured, so
# publishing runs via OIDC (id-token) with provenance — no token. We deliberately
# do NOT read NPM_TOKEN: a present-but-expired secret would be sent to the registry
# and 401 instead of falling back to OIDC, so the credential is left out entirely.

on:
  release:
    types:
      - published
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always

jobs:
  build-linux:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
    container:
      # Debian 11 bullseye => glibc 2.31 baseline (runs on RHEL 9 / Debian 11+ /
      # Ubuntu 20.04+). Pulled natively per arch on the matching runner.
      image: node:20-bullseye
    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: ubuntu-22.04
            target: x86_64-unknown-linux-gnu
          - runner: ubuntu-22.04-arm
            target: aarch64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4

      # rdkafka builds librdkafka from source (cc/make) and aws-lc-sys needs
      # cmake; the slim node image lacks these.
      - name: Install build toolchain
        run: |
          apt-get update -qq
          apt-get install -y --no-install-recommends build-essential cmake perl pkg-config

      - name: Install Rust toolchain
        run: |
          curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
          echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

      # Regenerate package.json/lock from the Cargo workspace version (source of
      # truth) BEFORE npm ci, so a drifted committed lock can't fail the build.
      - name: Sync version from Cargo workspace
        working-directory: node/mq-bridge-node
        run: npm run sync-version

      - name: Install Node dependencies
        working-directory: node/mq-bridge-node
        run: npm ci

      - name: Build native addon (full)
        working-directory: node/mq-bridge-node
        env:
          CARGO_NET_RETRY: "5"
          CARGO_NET_GIT_FETCH_WITH_CLI: "true"
          # curl's HTTP/2 multiplexing flakes on Windows runners ("[55] Failed
          # sending data to the peer"); force HTTP/1.1 so fetches are reliable.
          CARGO_HTTP_MULTIPLEXING: "false"
        run: >-
          npx napi build --platform --release --strip
          --target ${{ matrix.target }}
          --js native.js --dts native.d.ts

      - name: Upload addon
        uses: actions/upload-artifact@v4
        with:
          name: bindings-${{ matrix.target }}
          path: node/mq-bridge-node/*.node
          if-no-files-found: error

      # The root package ships the generated loader; upload it once.
      - name: Upload loader
        if: matrix.target == 'x86_64-unknown-linux-gnu'
        uses: actions/upload-artifact@v4
        with:
          name: loader
          path: |
            node/mq-bridge-node/native.js
            node/mq-bridge-node/native.d.ts
          if-no-files-found: error

  build-native:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: macos-15-intel
            target: x86_64-apple-darwin
          - runner: macos-latest
            target: aarch64-apple-darwin
          - runner: windows-latest
            target: x86_64-pc-windows-msvc
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm
          cache-dependency-path: node/mq-bridge-node/package-lock.json

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          targets: ${{ matrix.target }}

      # Regenerate package.json/lock from the Cargo workspace version (source of
      # truth) BEFORE npm ci, so a drifted committed lock can't fail the build.
      - name: Sync version from Cargo workspace
        working-directory: node/mq-bridge-node
        run: npm run sync-version

      - name: Install Node dependencies
        working-directory: node/mq-bridge-node
        run: npm ci

      - name: Build native addon (full)
        working-directory: node/mq-bridge-node
        env:
          CARGO_NET_RETRY: "5"
          CARGO_NET_GIT_FETCH_WITH_CLI: "true"
          # curl's HTTP/2 multiplexing flakes on Windows runners ("[55] Failed
          # sending data to the peer"); force HTTP/1.1 so fetches are reliable.
          CARGO_HTTP_MULTIPLEXING: "false"
        run: >-
          npx napi build --platform --release --strip
          --target ${{ matrix.target }}
          --js native.js --dts native.d.ts

      - name: Upload addon
        uses: actions/upload-artifact@v4
        with:
          name: bindings-${{ matrix.target }}
          path: node/mq-bridge-node/*.node
          if-no-files-found: error

  publish:
    name: Publish to npm
    runs-on: ubuntu-latest
    needs: [build-linux, build-native]
    permissions:
      contents: read
      id-token: write # OIDC trusted publishing + provenance
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          registry-url: "https://registry.npmjs.org"
          cache: npm
          cache-dependency-path: node/mq-bridge-node/package-lock.json

      # Trusted publishing (OIDC) requires npm >= 11.5.1.
      - name: Update npm
        run: npm install -g npm@11.5.1

      # Regenerate package.json/lock from the Cargo workspace version (source of
      # truth) BEFORE npm ci, so a drifted committed lock can't fail the publish.
      - name: Sync version from Cargo workspace
        working-directory: node/mq-bridge-node
        run: npm run sync-version

      - name: Install Node dependencies
        working-directory: node/mq-bridge-node
        run: npm ci

      - name: Download loader into package root
        uses: actions/download-artifact@v4
        with:
          name: loader
          path: node/mq-bridge-node

      - name: Download platform addons
        uses: actions/download-artifact@v4
        with:
          pattern: bindings-*
          path: node/mq-bridge-node/artifacts

      - name: Create per-platform npm dirs
        working-directory: node/mq-bridge-node
        run: npx napi create-npm-dirs

      - name: Distribute addons into npm dirs
        working-directory: node/mq-bridge-node
        run: npx napi artifacts -d artifacts

      - name: List npm dirs
        working-directory: node/mq-bridge-node
        run: ls -R npm

      # The default per-platform README is ~88 bytes; paired with a 50-67MB
      # binary that low-content signature trips npm's new-package spam filter
      # (E403 "Package name triggered spam detection"). Give each stub a real
      # README plus description/homepage/keywords so it doesn't read as a
      # placeholder. (Server-side heuristic; reduces but cannot guarantee a pass.)
      - name: Enrich platform package metadata
        working-directory: node/mq-bridge-node
        run: |
          set -e
          for dir in npm/*/; do
            name=$(node -p "require('./$dir/package.json').name")
            cat > "$dir/README.md" <<EOF
          # $name

          Prebuilt native binary for the [\`mq-bridge\`](https://github.com/marcomq/mq-bridge)
          Node.js addon (napi-rs). This is a platform-specific dependency of the
          \`mq-bridge\` package and is selected automatically via optionalDependencies —
          you install \`mq-bridge\`, not this package directly.
          EOF
            node -e "const p='./$dir/package.json',j=require(p);j.description='Prebuilt '+j.name+' binary for the mq-bridge Node.js addon';j.homepage='https://github.com/marcomq/mq-bridge#readme';j.keywords=['mq-bridge','napi','native','prebuilt','addon'];require('fs').writeFileSync(p,JSON.stringify(j,null,2)+'\n')"
          done

      - name: Publish platform packages and root
        working-directory: node/mq-bridge-node
        env:
          # No NODE_AUTH_TOKEN: publishing is OIDC-only (trusted publishing).
          NPM_CONFIG_PROVENANCE: "true"
        run: |
          set -e
          publish() {
            local dir="$1" name ver
            name=$(node -p "require('./$dir/package.json').name")
            ver=$(node -p "require('./$dir/package.json').version")
            # Skip if this exact version is already on the registry; a re-run
            # after a partial publish must not fail on already-published dirs.
            if npm view "$name@$ver" version >/dev/null 2>&1; then
              echo "Skipping $name@$ver (already published)"
              return 0
            fi
            echo "Publishing $dir ($name@$ver)"
            npm publish "$dir" --access public
          }
          for dir in npm/*/; do
            publish "$dir"
          done
          echo "Publishing root package"
          root_name=$(node -p "require('./package.json').name")
          root_ver=$(node -p "require('./package.json').version")
          if npm view "$root_name@$root_ver" version >/dev/null 2>&1; then
            echo "Skipping $root_name@$root_ver (already published)"
          else
            npm publish --access public
          fi