infino 0.4.1

A fast retrieval engine that stores data on object storage and runs SQL, full-text search, and vector search over it from a single system — search-on-Parquet.
Documentation
name: Node publish

# Builds the prebuilt addon for every supported platform, then publishes the
# thin `infino` package plus its per-platform binary packages (`infino-<triple>`)
# to npm.
#
# Triggered two ways: (1) a `v<version>` tag — but ONLY for a coordinated
# minor/major release (patch == 0, e.g. `v0.2.0`); a crate-only patch tag (e.g.
# `v0.1.1`) skips the bindings, which patch independently. The version comes from
# package.json and a guard asserts it matches the tag. (2) a manual run
# (workflow_dispatch) — the path for an independent Node patch — defaults to a
# dry run; re-run with dry_run unchecked to publish. A real run needs the
# NPM_TOKEN secret, with publish rights to the packages under the infino-ai org.
#
# major.minor is locked in sync across crate/Node/Python (coordinated `vX.Y.0`
# releases); patch is independent per package, so the crate's patch counter and
# Node's never share a number — see docs/versioning.md.

on:
  push:
    tags: ["v[0-9]*"]
  workflow_dispatch:
    inputs:
      dry_run:
        description: "Validate only — pack everything, publish nothing"
        type: boolean
        default: true

env:
  CARGO_TERM_COLOR: always
  # The binding builds infino as a normal dependency; don't fail on a
  # transitive-dependency deprecation warning (same as the CI node jobs).
  RUSTFLAGS: ""

jobs:
  build:
    name: Build addon (${{ matrix.target }})
    # Coordinated-release gate: on a tag push, run only when the tag is a
    # minor/major (patch == 0, tag ends in `.0`). A crate-only patch tag skips
    # the bindings. A manual run always proceeds (independent Node patch).
    # Never run in a fork: only this repo publishes to the registry.
    if: ${{ github.repository == 'infino-ai/infino' && (github.event_name == 'workflow_dispatch' || endsWith(github.ref_name, '.0')) }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      # glibc + macOS build natively, one runner per target (`make node-build`
      # builds for the host). musl (Alpine) has no native GitHub runner, so it
      # builds inside the napi-rs Alpine image where the system `cc` is musl —
      # the crate has C-based deps (e.g. zstd), so a native musl toolchain is
      # simpler and more reliable than cross-compiling from glibc.
      matrix:
        include:
          - { os: ubuntu-latest, target: linux-x64-gnu, glibc_image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian }
          - { os: ubuntu-24.04-arm, target: linux-arm64-gnu, glibc_image: node:20-bullseye }
          - { os: macos-15-intel, target: darwin-x64 }
          - { os: macos-14, target: darwin-arm64 }
          - { os: ubuntu-latest, target: linux-x64-musl, musl: x86_64-unknown-linux-musl }
          - { os: ubuntu-24.04-arm, target: linux-arm64-musl, musl: aarch64-unknown-linux-musl }
    steps:
      - uses: actions/checkout@v4
      - name: Free disk space
        if: runner.os == 'Linux'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      # --- native build (glibc / macOS) ---
      - uses: dtolnay/rust-toolchain@stable
        if: ${{ !matrix.musl && !matrix.glibc_image }}
      - uses: actions/setup-node@v4
        if: ${{ !matrix.musl }}
        with:
          node-version: "20"
      - uses: Swatinem/rust-cache@v2
        if: ${{ !matrix.musl && !matrix.glibc_image }}
        with:
          workspaces: infino-node
      - name: Build addon (native)
        if: ${{ !matrix.musl && !matrix.glibc_image }}
        run: make node-build
      # --- glibc build (older-baseline container) ---
      # Linking on the ubuntu-24.04 runners stamps the addon with glibc 2.39
      # symbols, so it fails to load on Debian 12 / Ubuntu 22.04 / the default
      # node:22-slim image. Building inside a Debian bullseye container pins
      # the floor at glibc 2.31: the napi-rs image on x64, and the multi-arch
      # node:20-bullseye (rustup bootstrapped in-run) on arm64 — the napi-rs
      # *-aarch64 tags are amd64-hosted cross-compile images and exec-fail on
      # native arm runners. The verify step below then loads the artifact on
      # the newer host runner, covering forward compatibility.
      - name: Build addon (glibc 2.31 baseline, in Debian container)
        if: ${{ matrix.glibc_image }}
        run: |
          docker run --rm -v "$PWD:/build" -w /build/infino-node \
            ${{ matrix.glibc_image }} \
            bash -c "command -v cargo >/dev/null || \
                       (curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal); \
                     export PATH=\$HOME/.cargo/bin:\$PATH && \
                     npm install --ignore-scripts && \
                     npx napi build --platform --release --strip --js native.js --dts native.d.ts infino && \
                     npx tsc"
      # The container runs as root, so everything it wrote on the mounted
      # workspace (node_modules, target/, the addon) is root-owned — the
      # host-side verify step then fails with EACCES. Hand it all back.
      - name: Restore workspace ownership after container build
        if: ${{ matrix.glibc_image }}
        run: sudo chown -R "$(id -u):$(id -g)" .
      - name: List container build outputs
        if: ${{ matrix.glibc_image }}
        run: ls -la infino-node/infino/ || true
      # Smoke-test the AS-SHIPPED package on this platform: pack the tarballs,
      # install them into a throwaway project, and run a real query — proving the
      # loader resolves the per-platform binary from a clean install (not the
      # sibling build `npm test` uses). Reuses the build above (no rebuild).
      - name: Verify packaged binary (native)
        if: ${{ !matrix.musl }}
        run: make node-verify
      # Runs AFTER verify on purpose: verify-pack rebuilds the addon on the
      # host if any generated file is missing, which would silently raise the
      # glibc floor of the artifact that gets uploaded. Checking last means
      # that failure mode breaks the build instead of shipping.
      - name: Check binary glibc floor
        if: ${{ matrix.glibc_image }}
        run: |
          MAX=$(objdump -T infino-node/infino/*.node | grep -oE 'GLIBC_[0-9.]+' | sort -Vu | tail -1)
          echo "max glibc symbol: $MAX"
          [ "$(printf '%s\n' "$MAX" GLIBC_2.31 | sort -V | tail -1)" = "GLIBC_2.31" ]
      # --- musl build (Alpine, via the napi-rs builder image; cc is musl) ---
      # Runs on the host runner (so the disk-free step above still applies) but
      # compiles inside Alpine, where the target arch matches the runner arch —
      # so this is a native musl build, not a cross-compile. Same install→load→
      # query smoke-test as the native legs, run inside the container (bash is
      # not in the base image, so add it for verify-pack.sh).
      - name: Build addon (musl, in Alpine) + verify packaged binary
        if: ${{ matrix.musl }}
        run: |
          docker run --rm -v "$PWD:/build" -w /build/infino-node \
            ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine \
            sh -c "apk add --no-cache bash >/dev/null && \
                   rustup target add ${{ matrix.musl }} && \
                   npm install --ignore-scripts && \
                   npx napi build --platform --release --strip --target ${{ matrix.musl }} --js native.js --dts native.d.ts infino && \
                   npx tsc && \
                   bash ./scripts/verify-pack.sh"
      - name: Upload addon
        uses: actions/upload-artifact@v4
        with:
          name: addon-${{ matrix.target }}
          path: infino-node/infino/*.node
          if-no-files-found: error
      # The JS loader and compiled wrapper are identical across platforms;
      # upload them once so the publish job needs no Rust build.
      - name: Upload JS bindings
        if: matrix.target == 'linux-x64-gnu'
        uses: actions/upload-artifact@v4
        with:
          name: js-bindings
          path: |
            infino-node/infino/native.js
            infino-node/infino/native.d.ts
            infino-node/infino/index.js
            infino-node/infino/index.d.ts
          if-no-files-found: error

  publish:
    name: Publish to npm
    needs: build
    if: ${{ github.event_name == 'workflow_dispatch' || endsWith(github.ref_name, '.0') }}
    runs-on: ubuntu-latest
    # OIDC token for npm provenance — the "built from this repo at this commit"
    # attestation shown on npmjs.org. A real supply-chain trust signal at launch.
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      # On a tag push the published version comes from package.json — assert it
      # matches the tag so a coordinated release can't ship a mismatched version.
      - name: Check package.json version matches tag
        if: ${{ github.event_name == 'push' }}
        run: |
          tag="${GITHUB_REF_NAME#v}"
          pkg="$(jq -r .version infino-node/package.json)"
          if [ "$tag" != "$pkg" ]; then
            echo "::error::tag v$tag does not match infino-node/package.json version $pkg"
            exit 1
          fi
      # The version about to publish is package.json's — assert it sits on the
      # crate's major.minor release line and the infx-* pins track it
      # (docs/versioning.md). Guards manual dispatches from a branch that
      # never went through the PR gate.
      - name: Check versions against the crate's release line
        run: python3 scripts/check_version_sync.py
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          registry-url: "https://registry.npmjs.org"
      # All platform binaries land flattened into infino-node/artifacts/;
      # `napi artifacts` matches each by the triple in its filename.
      - name: Download platform addons
        uses: actions/download-artifact@v4
        with:
          pattern: addon-*
          path: infino-node/artifacts
          merge-multiple: true
      - name: Download JS bindings
        uses: actions/download-artifact@v4
        with:
          name: js-bindings
          path: infino-node/infino
      - name: Install tooling
        run: cd infino-node && npm install --ignore-scripts
      # npm/ isn't committed — generate the per-platform package dirs from
      # package.json (triples, version, description) before staging binaries.
      - name: Generate platform package dirs
        run: cd infino-node && npx napi create-npm-dir -t .
      # Copy each binary into its per-platform package under npm/<triple>/.
      - name: Stage binaries into platform packages
        run: cd infino-node && npm run artifacts
      - name: Validate (dry run)
        if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
        run: |
          cd infino-node
          npx napi prepublish -t npm --skip-gh-release --dry-run
          npm publish --dry-run --tag latest --access public
      # Publishes the per-platform packages and pins them in the main
      # package's optionalDependencies, then publishes the main package.
      # `--tag latest` is required (not just default): the name's history
      # includes a higher version, so npm won't move the latest tag onto
      # 0.1.0 implicitly. Passing it explicitly sets latest to the engine.
      - name: Publish to npm
        if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.dry_run) }}
        run: |
          cd infino-node
          npx napi prepublish -t npm --skip-gh-release
          npm publish --tag latest --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          # Emit provenance for the main package AND every per-platform package
          # (napi's prepublish shells out to `npm publish`, which honors this).
          NPM_CONFIG_PROVENANCE: "true"