Files
anyllm-proxy/.github/workflows/ci.yml
T
whit3rabbitandClaude Opus 4.8 ff157dda81 ci: publish new leaf crates to crates.io before proxy
The 0.14.0 proxy gained hard deps on anyllm_pxpipe, anyllm_rtk, and the
anyllm_optimize_* crates, none of which the publish job uploaded. Since
none exist on crates.io, cargo publish -p anyllm_proxy would fail on the
missing deps, and the exit-101 guard (cargo returns 101 for nearly all
errors) masked it as "already published" - proxy never landed while the
job stayed green.

Add publish steps for pxpipe, rtk, optimize_core/passes/scorer in
dependency order before anyllm_proxy, and document the invariant in
CLAUDE.md so future leaf crates get added here too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:31:37 -05:00

438 lines
16 KiB
YAML

name: CI
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: crates/proxy/admin-ui/package-lock.json
- name: Install dependencies
working-directory: crates/proxy/admin-ui
run: npm ci --legacy-peer-deps
- name: TypeScript check
working-directory: crates/proxy/admin-ui
run: npx tsc --noEmit
- name: Lint
working-directory: crates/proxy/admin-ui
run: npm run lint
- name: Build
working-directory: crates/proxy/admin-ui
run: npm run build
- name: Upload dist
uses: actions/upload-artifact@v7
with:
name: admin-ui-dist
path: crates/proxy/admin-ui/dist/
test:
needs: frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Download dist
uses: actions/download-artifact@v8
with:
name: admin-ui-dist
path: crates/proxy/admin-ui/dist/
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Build
run: cargo build
- name: Test
run: cargo test
# Optimizer ONNX scorer (opt-in feature). Compile + lint + model-free lib tests so
# the feature-gated code cannot rot. NOT `cargo test` on the whole workspace: the
# parity integration test (optimize-scorer/tests/parity.rs) is intentionally not
# #[ignore] and panics without the ~170MB model artifact, so only the lib unit tests
# (which need no artifact) run here. `ort` downloads an onnxruntime binary at build.
- name: Clippy (optimizer-onnx)
run: cargo clippy -p anyllm_proxy --features optimizer-onnx -- -D warnings
- name: Test optimizer-onnx (lib only, no model artifact)
run: cargo test -p anyllm_optimize_scorer --features onnx --lib
- name: Security audit
run: |
cargo install cargo-audit --locked --quiet
cargo audit
build-release:
name: Build (${{ matrix.target }})
needs: test
if: startsWith(github.ref, 'refs/tags/v')
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
binary: anyllm-proxy
deb_arch: amd64
# Linux ARM64 (native GitHub runner)
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: anyllm-proxy
deb_arch: arm64
# macOS Apple Silicon
- os: macos-latest
target: aarch64-apple-darwin
binary: anyllm-proxy
# macOS Intel
- os: macos-latest
target: x86_64-apple-darwin
binary: anyllm-proxy
# Windows x86_64
- os: windows-2025
target: x86_64-pc-windows-msvc
binary: anyllm-proxy.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Download dist
uses: actions/download-artifact@v8
with:
name: admin-ui-dist
path: crates/proxy/admin-ui/dist/
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Build
run: cargo build --release -p anyllm_proxy --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: anyllm_proxy-${{ matrix.target }}
path: target/${{ matrix.target }}/release/${{ matrix.binary }}
- name: Build deb package
if: matrix.deb_arch != ''
run: |
cargo install cargo-deb --locked --quiet
cargo deb -p anyllm_proxy --no-build --no-strip --target ${{ matrix.target }}
- name: Upload deb artifact
if: matrix.deb_arch != ''
uses: actions/upload-artifact@v7
with:
name: anyllm-proxy-deb-${{ matrix.deb_arch }}
path: target/${{ matrix.target }}/debian/*.deb
test-deb:
name: Test deb (${{ matrix.arch }})
needs: build-release
if: startsWith(github.ref, 'refs/tags/v')
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
os: ubuntu-latest
- arch: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
steps:
- name: Download deb
uses: actions/download-artifact@v8
with:
name: anyllm-proxy-deb-${{ matrix.arch }}
path: ./deb
- name: Inspect package
run: |
dpkg-deb --info ./deb/*.deb
dpkg-deb --contents ./deb/*.deb
- name: Lint package
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq lintian
lintian --no-tag-display-limit ./deb/*.deb || true
- name: Install package
run: |
sudo dpkg -i ./deb/*.deb || true
sudo apt-get install -f -y
- name: Verify binary
run: |
test -x /usr/bin/anyllm-proxy
file /usr/bin/anyllm-proxy | grep -q "ELF"
- name: Verify systemd unit
run: |
systemd-analyze verify /lib/systemd/system/anyllm-proxy.service
- name: Verify postinst artifacts
run: |
getent passwd anyllm
test -d /var/lib/anyllm
stat -c '%U:%G' /var/lib/anyllm | grep -q 'anyllm:anyllm'
- name: Verify config file
run: |
test -f /etc/default/anyllm-proxy
create-release:
name: Create GitHub Release
needs: [test-deb]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Extract changelog section
id: changelog
run: |
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
# Extract lines between the matching version header and the next version header.
NOTES=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md)
if [ -z "$NOTES" ]; then
NOTES="See CHANGELOG.md for details."
fi
printf '%s' "$NOTES" > release_notes.txt
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
--repo "${{ github.repository }}" \
--notes-file release_notes.txt \
--title "anyllm-proxy ${{ github.ref_name }}" \
|| echo "Release already exists, continuing"
release-assets:
name: Upload release assets
needs: [create-release, build-release]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download amd64 deb
uses: actions/download-artifact@v8
with:
name: anyllm-proxy-deb-amd64
path: ./debs
- name: Download arm64 deb
uses: actions/download-artifact@v8
with:
name: anyllm-proxy-deb-arm64
path: ./debs
- name: Upload debs to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
ls -la ./debs/
gh release upload "${{ github.ref_name }}" ./debs/*.deb --repo "${{ github.repository }}" --clobber
# Linux + Windows binaries are built by build-release but only uploaded as
# CI artifacts; package them into release archives (tar.gz / zip) matching
# the macOS tarball naming produced by brew-release.
- name: Download linux x86_64 binary
uses: actions/download-artifact@v8
with:
name: anyllm_proxy-x86_64-unknown-linux-gnu
path: ./bins/linux-x86_64
- name: Download linux arm64 binary
uses: actions/download-artifact@v8
with:
name: anyllm_proxy-aarch64-unknown-linux-gnu
path: ./bins/linux-arm64
- name: Download windows x86_64 binary
uses: actions/download-artifact@v8
with:
name: anyllm_proxy-x86_64-pc-windows-msvc
path: ./bins/windows-x86_64
- name: Package binary archives
run: |
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
chmod +x ./bins/linux-x86_64/anyllm-proxy ./bins/linux-arm64/anyllm-proxy
mkdir -p dist
tar -czf "dist/anyllm-proxy-${VERSION}-linux-x86_64.tar.gz" -C ./bins/linux-x86_64 anyllm-proxy
tar -czf "dist/anyllm-proxy-${VERSION}-linux-arm64.tar.gz" -C ./bins/linux-arm64 anyllm-proxy
( cd ./bins/windows-x86_64 && zip -q "${GITHUB_WORKSPACE}/dist/anyllm-proxy-${VERSION}-windows-x86_64.zip" anyllm-proxy.exe )
ls -la dist/
- name: Upload binary archives to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release upload "${{ github.ref_name }}" dist/*.tar.gz dist/*.zip --repo "${{ github.repository }}" --clobber
publish:
name: Publish to crates.io
needs: [test, build-release]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# Publish in dependency order. --no-verify skips re-building from the
# packed tarball; the test job already verified the build.
# Sleeps give the crates.io index time to propagate before dependents publish.
# exit 101 = version already exists; the per-step shell swallows it so
# re-runs are idempotent without masking real failures.
- name: Publish anyllm_translate
run: cargo publish -p anyllm_translate || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_providers
run: cargo publish -p anyllm_providers --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_client
run: cargo publish -p anyllm_client --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_batch_engine
run: cargo publish -p anyllm_batch_engine --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_pxpipe
run: cargo publish -p anyllm_pxpipe --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_rtk
run: cargo publish -p anyllm_rtk --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_optimize_core
run: cargo publish -p anyllm_optimize_core --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_optimize_passes
run: cargo publish -p anyllm_optimize_passes --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_optimize_scorer
run: cargo publish -p anyllm_optimize_scorer --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for index propagation
run: sleep 30
- name: Publish anyllm_proxy
run: cargo publish -p anyllm_proxy --no-verify || { ec=$?; [ "$ec" -eq 101 ] && echo "already published" || exit "$ec"; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
brew-release:
name: Homebrew tap release
needs: [create-release]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download arm64 macOS binary
uses: actions/download-artifact@v8
with:
name: anyllm_proxy-aarch64-apple-darwin
path: ./macos/arm64
- name: Download x86_64 macOS binary
uses: actions/download-artifact@v8
with:
name: anyllm_proxy-x86_64-apple-darwin
path: ./macos/x86_64
- name: Create versioned tarballs
id: tarballs
run: |
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
chmod +x ./macos/arm64/anyllm-proxy ./macos/x86_64/anyllm-proxy
mkdir -p dist
tar -czf "dist/anyllm-proxy-${VERSION}-macos-arm64.tar.gz" -C ./macos/arm64 anyllm-proxy
tar -czf "dist/anyllm-proxy-${VERSION}-macos-x86_64.tar.gz" -C ./macos/x86_64 anyllm-proxy
- name: Upload macOS tarballs to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.tarballs.outputs.version }}"
gh release upload "${{ github.ref_name }}" \
"dist/anyllm-proxy-${VERSION}-macos-arm64.tar.gz" \
"dist/anyllm-proxy-${VERSION}-macos-x86_64.tar.gz" \
--repo "${{ github.repository }}" --clobber
- name: Compute SHA256
id: sha
run: |
VERSION="${{ steps.tarballs.outputs.version }}"
ARM64=$(sha256sum "dist/anyllm-proxy-${VERSION}-macos-arm64.tar.gz" | cut -d' ' -f1)
X86=$(sha256sum "dist/anyllm-proxy-${VERSION}-macos-x86_64.tar.gz" | cut -d' ' -f1)
echo "arm64=${ARM64}" >> "$GITHUB_OUTPUT"
echo "x86_64=${X86}" >> "$GITHUB_OUTPUT"
- name: Update Homebrew tap
env:
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
if [ -z "${HOMEBREW_TAP_TOKEN}" ]; then
echo "HOMEBREW_TAP_TOKEN not set, skipping Homebrew tap update"
exit 0
fi
VERSION="${{ steps.tarballs.outputs.version }}"
ARM64_SHA="${{ steps.sha.outputs.arm64 }}"
X86_SHA="${{ steps.sha.outputs.x86_64 }}"
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/whit3rabbit/homebrew-tap.git"
cd homebrew-tap
{
printf '%s\n' 'cask "anyllm-proxy" do'
printf '%s\n' ' arch arm: "arm64", intel: "x86_64"'
printf '%s\n' ''
printf " version \"%s\"\n" "${VERSION}"
printf " sha256 arm: \"%s\",\n" "${ARM64_SHA}"
printf " intel: \"%s\"\n" "${X86_SHA}"
printf '%s\n' ''
printf '%s\n' ' url "https://github.com/whit3rabbit/anyllm-proxy/releases/download/v#{version}/anyllm-proxy-#{version}-macos-#{arch}.tar.gz"'
printf '%s\n' ' name "anyllm-proxy"'
printf '%s\n' ' desc "HTTP proxy translating Anthropic Messages API and OpenAI Chat Completions to any backend"'
printf '%s\n' ' homepage "https://github.com/whit3rabbit/anyllm-proxy"'
printf '%s\n' ''
printf '%s\n' ' binary "anyllm-proxy"'
printf '%s\n' 'end'
} > Casks/anyllm-proxy.rb
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git add Casks/anyllm-proxy.rb
git diff --staged --quiet || git commit -m "chore: update anyllm-proxy to ${VERSION}"
git push