Skip to content

Release

Release #10

Workflow file for this run

name: Release
# First-release pipeline (M1): Rust crates → crates.io, Node → npm,
# Python → PyPI (trusted publishing), Go .a artifacts + C ABI artifacts →
# GitHub Release. Swift / Kotlin / Flutter land in later milestones.
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
rust:
description: 'Publish Rust crates to crates.io'
type: boolean
default: true
node:
description: 'Build + publish Node binding to npm'
type: boolean
default: false
python:
description: 'Publish Python wheels to PyPI'
type: boolean
default: false
jvm:
description: 'Publish Java + Kotlin to Maven Central'
type: boolean
default: false
artifacts:
description: 'Build Go .a + C ABI libs and create GitHub Release'
type: boolean
default: false
permissions:
contents: write
id-token: write # npm provenance + PyPI trusted publishing
env:
CARGO_TERM_COLOR: always
jobs:
# ── Rust core → crates.io (dependency order) ─────────────────────────────
rust-publish:
name: Publish crates to crates.io
runs-on: ubuntu-latest
environment: release
if: github.event_name == 'push' || github.event.inputs.rust == 'true'
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
# Idempotent publish: skips a crate whose version already exists on
# crates.io, so re-running the workflow after a partial failure is safe.
# The version is read from the workspace Cargo.toml so this never goes
# stale across releases.
- name: Publish aimux-stream
run: |
VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
if curl -sf -A "aimux-ci" "https://crates.io/api/v1/crates/aimux-stream/${VERSION}" > /dev/null 2>&1; then
echo "aimux-stream@${VERSION} already published, skipping"
else
cargo publish -p aimux-stream
fi
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Publish aimux-core
run: |
sleep 30
VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
if curl -sf -A "aimux-ci" "https://crates.io/api/v1/crates/aimux-core/${VERSION}" > /dev/null 2>&1; then
echo "aimux-core@${VERSION} already published, skipping"
else
cargo publish -p aimux-core
fi
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Publish aimux-provider-utils
run: |
sleep 30
VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
if curl -sf -A "aimux-ci" "https://crates.io/api/v1/crates/aimux-provider-utils/${VERSION}" > /dev/null 2>&1; then
echo "aimux-provider-utils@${VERSION} already published, skipping"
else
cargo publish -p aimux-provider-utils
fi
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Publish aimux-providers
run: |
sleep 30
VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
if curl -sf -A "aimux-ci" "https://crates.io/api/v1/crates/aimux-providers/${VERSION}" > /dev/null 2>&1; then
echo "aimux-providers@${VERSION} already published, skipping"
else
cargo publish -p aimux-providers
fi
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Publish aimux-ffi
run: |
sleep 30
VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/')
if curl -sf -A "aimux-ci" "https://crates.io/api/v1/crates/aimux-ffi/${VERSION}" > /dev/null 2>&1; then
echo "aimux-ffi@${VERSION} already published, skipping"
else
cargo publish -p aimux-ffi
fi
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
# ── Node binding: build per-platform .node, then publish to npm ──────────
node-build:
name: Node build (${{ matrix.settings.target }})
runs-on: ${{ matrix.settings.host }}
env:
MACOSX_DEPLOYMENT_TARGET: '10.13'
if: github.event_name == 'push' || github.event.inputs.node == 'true'
strategy:
fail-fast: false
matrix:
settings:
- host: windows-latest
target: x86_64-pc-windows-msvc
binary: aimux.win32-x64-msvc.node
- host: windows-latest
target: aarch64-pc-windows-msvc
binary: aimux.win32-arm64-msvc.node
- host: macos-15-intel
target: x86_64-apple-darwin
binary: aimux.darwin-x64.node
- host: macos-latest
target: aarch64-apple-darwin
binary: aimux.darwin-arm64.node
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: aimux.linux-x64-gnu.node
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
binary: aimux.linux-arm64-gnu.node
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.target }}
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies
working-directory: bindings/node
run: npm ci
- name: Build Linux (glibc 2.17 baseline)
if: contains(matrix.settings.target, 'unknown-linux-gnu')
working-directory: bindings/node
shell: bash
env:
TARGET_CC: clang
TARGET_CXX: clang++
run: npx napi build --platform --release --target ${{ matrix.settings.target }} --use-napi-cross
- name: Build
if: ${{ !contains(matrix.settings.target, 'unknown-linux-gnu') }}
working-directory: bindings/node
shell: bash
run: npx napi build --platform --release --target ${{ matrix.settings.target }}
- name: Verify artifact
working-directory: bindings/node
shell: bash
run: test -f '${{ matrix.settings.binary }}'
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: node-${{ matrix.settings.target }}
path: bindings/node/*.node
node-publish:
name: Publish Node to npm
needs: node-build
runs-on: ubuntu-latest
environment: release
if: github.event_name == 'push' || github.event.inputs.node == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Install dependencies
working-directory: bindings/node
run: npm ci
- name: Create platform packages
working-directory: bindings/node
run: npx napi create-npm-dirs
- name: Download platform binaries
uses: actions/download-artifact@v4
with:
path: bindings/node/artifacts
pattern: node-*
- name: Collect and verify platform packages
working-directory: bindings/node
shell: bash
run: |
npx napi artifacts --output-dir artifacts --npm-dir npm
expected=(
npm/win32-x64-msvc/aimux.win32-x64-msvc.node
npm/win32-arm64-msvc/aimux.win32-arm64-msvc.node
npm/darwin-x64/aimux.darwin-x64.node
npm/darwin-arm64/aimux.darwin-arm64.node
npm/linux-x64-gnu/aimux.linux-x64-gnu.node
npm/linux-arm64-gnu/aimux.linux-arm64-gnu.node
)
for binary in "${expected[@]}"; do
test -f "$binary" || { echo "Missing $binary" >&2; exit 1; }
done
test "$(find npm -type f -name 'aimux.*.node' | wc -l | tr -d ' ')" = 6
- name: Publish
working-directory: bindings/node
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: "true"
NPM_CONFIG_ACCESS: public
# ── Python binding: build + publish platform wheels (OIDC trusted) ───────
python-release:
name: Publish Python (${{ matrix.os }})
runs-on: ${{ matrix.os }}
if: github.event_name == 'push' || github.event.inputs.python == 'true'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build and publish wheel
uses: PyO3/maturin-action@v1
with:
command: publish
args: --no-sdist
working-directory: bindings/python
# ── Go binding: build libaimux_ffi.a per platform, ship on GitHub Release
# (users download the archive for their platform via `go generate`).
# NOTE: Windows uses the gnu target — Go's cgo links with mingw gcc,
# which cannot consume MSVC-format .a archives. ─────────────────────────
go-build:
name: Go binding (${{ matrix.settings.target }})
runs-on: ${{ matrix.settings.host }}
if: github.event_name == 'push' || github.event.inputs.artifacts == 'true'
strategy:
fail-fast: false
matrix:
settings:
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
- host: macos-13
target: x86_64-apple-darwin
- host: macos-latest
target: aarch64-apple-darwin
- host: windows-latest
target: x86_64-pc-windows-gnu
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.target }}
- uses: Swatinem/rust-cache@v2
- name: Build static archive
run: cargo build -p aimux-ffi --release --target ${{ matrix.settings.target }}
- name: Test Go binding
working-directory: bindings/go
shell: bash
run: |
export CGO_LDFLAGS="-L${{ github.workspace }}/target/${{ matrix.settings.target }}/release"
go vet ./...
go test ./...
- name: Stage archive with platform name
shell: bash
run: |
mkdir -p staging
cp target/${{ matrix.settings.target }}/release/libaimux_ffi.a staging/libaimux_ffi-${{ matrix.settings.target }}.a
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: libaimux_ffi-${{ matrix.settings.target }}.a
path: staging/libaimux_ffi-${{ matrix.settings.target }}.a
# ── C ABI shared libraries (C/C++ + reference for other bindings) ────────
ffi-build:
name: aimux-ffi (${{ matrix.target }})
runs-on: ${{ matrix.host }}
if: github.event_name == 'push' || github.event.inputs.artifacts == 'true'
strategy:
fail-fast: false
matrix:
include:
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact: libaimux_ffi-linux-x64.so
- host: macos-latest
target: aarch64-apple-darwin
artifact: libaimux_ffi-macos-arm64.dylib
- host: windows-latest
target: x86_64-pc-windows-msvc
artifact: aimux_ffi-windows-x64.dll
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Build cdylib
run: cargo build -p aimux-ffi --release --target ${{ matrix.target }}
- name: Stage library with platform name
shell: bash
run: |
mkdir -p staging
case "${{ matrix.target }}" in
*linux*) cp target/${{ matrix.target }}/release/libaimux_ffi.so staging/${{ matrix.artifact }} ;;
*darwin*) cp target/${{ matrix.target }}/release/libaimux_ffi.dylib staging/${{ matrix.artifact }} ;;
*windows*) cp target/${{ matrix.target }}/release/aimux_ffi.dll staging/${{ matrix.artifact }} ;;
esac
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: staging/${{ matrix.artifact }}
# ── JVM bindings: publish aimux-java + aimux-kotlin to Maven Central ──────
jvm-publish:
name: Publish Java+Kotlin to Maven Central
runs-on: ubuntu-latest
environment: release
if: github.event_name == 'push' || github.event.inputs.jvm == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: gradle/actions/setup-gradle@v4
- name: Publish aimux-java
working-directory: bindings/java
run: gradle publish
env:
ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
- name: Publish aimux-kotlin
working-directory: bindings/kotlin
run: gradle publish
env:
ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
# ── GitHub Release: aggregate all binary artifacts (C ABI libs + Go .a) ──
github-release:
name: Create GitHub Release
needs: [go-build, ffi-build]
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.inputs.artifacts == 'true'
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true
- name: List assets
run: ls -la release-assets/
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: release-assets/**
generate_release_notes: true