release: prepare v0.6.1 (#564) #29
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*.*.*' | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_NET_RETRY: 10 | |
| RUST_BACKTRACE: short | |
| RUSTUP_MAX_RETRIES: 10 | |
| permissions: | |
| contents: write | |
| id-token: write # Required for trusted publishing (PyPI OIDC, npm provenance) | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| # Validate tag and extract version | |
| validate: | |
| name: Validate Release Tag | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| TAG="${{ github.ref_name }}" | |
| VERSION="${TAG#v}" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Releasing version: $VERSION" | |
| - name: Verify version matches Cargo.toml | |
| run: | | |
| CARGO_VERSION=$(grep -m 1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/') | |
| TAG_VERSION="${{ steps.version.outputs.version }}" | |
| if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then | |
| echo "Error: Cargo.toml version ($CARGO_VERSION) does not match tag version ($TAG_VERSION)" | |
| exit 1 | |
| fi | |
| echo "Version validation successful: $TAG_VERSION" | |
| # Build and publish Rust crates to crates.io using Trusted Publishing (OIDC) | |
| # Setup: https://crates.io/docs/trusted-publishing | |
| publish-crates: | |
| name: Publish to crates.io | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| id-token: write # Required for trusted publishing | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache Cargo dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "release" | |
| - name: Verify packages can be built | |
| run: | | |
| cargo build -p exarch-core --release --all-features | |
| cargo build -p exarch-cli --release --all-features | |
| - name: Run tests | |
| run: | | |
| cargo test -p exarch-core --all-features | |
| cargo test -p exarch-cli --all-features | |
| - name: Authenticate with crates.io (Trusted Publishing) | |
| id: crates-io-auth | |
| uses: rust-lang/crates-io-auth-action@v1 | |
| - name: Publish crates to crates.io | |
| uses: katyo/publish-crates@v2 | |
| with: | |
| registry-token: ${{ steps.crates-io-auth.outputs.token }} | |
| ignore-unpublished-changes: true | |
| publish-delay: 10000 | |
| # Build Python wheels for multiple platforms | |
| build-python-wheels: | |
| name: Build Python Wheels (${{ matrix.platform.name }}) | |
| needs: validate | |
| runs-on: ${{ matrix.platform.os }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| # Linux x86_64 | |
| - name: linux-x86_64 | |
| os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| maturin-args: --target x86_64-unknown-linux-gnu | |
| # Linux aarch64 | |
| - name: linux-aarch64 | |
| os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| maturin-args: --target aarch64-unknown-linux-gnu | |
| # Linux x86_64 (musl) | |
| - name: linux-musl-x86_64 | |
| os: ubuntu-latest | |
| target: x86_64-unknown-linux-musl | |
| manylinux: musllinux_1_2 | |
| # Linux aarch64 (musl) | |
| - name: linux-musl-aarch64 | |
| os: ubuntu-latest | |
| target: aarch64-unknown-linux-musl | |
| manylinux: musllinux_1_2 | |
| # macOS x86_64 | |
| - name: macos-x86_64 | |
| os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| maturin-args: --target x86_64-apple-darwin | |
| # macOS aarch64 (Apple Silicon) | |
| - name: macos-aarch64 | |
| os: macos-latest | |
| target: aarch64-apple-darwin | |
| maturin-args: --target aarch64-apple-darwin | |
| # Windows x86_64 | |
| - name: windows-x86_64 | |
| os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| maturin-args: --target x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.platform.target }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.10' | |
| - name: Install maturin | |
| run: pip install maturin | |
| - name: Setup cross-compilation (Linux aarch64) | |
| if: matrix.platform.name == 'linux-aarch64' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV | |
| - name: Cache Cargo dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "python-${{ matrix.platform.name }}" | |
| - name: Build wheels (musl) | |
| if: matrix.platform.manylinux | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: ${{ matrix.platform.target }} | |
| manylinux: ${{ matrix.platform.manylinux }} | |
| args: --release --strip --out wheels -i python3.10 | |
| working-directory: crates/exarch-python | |
| - name: Build wheels | |
| if: ${{ !matrix.platform.manylinux }} | |
| working-directory: crates/exarch-python | |
| run: maturin build --release ${{ matrix.platform.maturin-args }} --strip --out wheels -i python3.10 | |
| - name: Upload wheels | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-${{ matrix.platform.name }} | |
| path: crates/exarch-python/wheels/*.whl | |
| retention-days: 7 | |
| # Build exarch-cli binaries for multiple platforms | |
| build-cli-binaries: | |
| name: Build CLI Binary (${{ matrix.platform.name }}) | |
| needs: validate | |
| runs-on: ${{ matrix.platform.os }} | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| # Linux x86_64 | |
| - name: linux-x86_64 | |
| os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| archive: tar.gz | |
| # Linux aarch64 | |
| - name: linux-aarch64 | |
| os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| archive: tar.gz | |
| # macOS x86_64 | |
| - name: macos-x86_64 | |
| os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| archive: tar.gz | |
| # macOS aarch64 (Apple Silicon) | |
| - name: macos-aarch64 | |
| os: macos-latest | |
| target: aarch64-apple-darwin | |
| archive: tar.gz | |
| # Windows x86_64 | |
| - name: windows-x86_64 | |
| os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| archive: zip | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.platform.target }} | |
| - name: Setup cross-compilation (Linux aarch64) | |
| if: matrix.platform.name == 'linux-aarch64' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV | |
| - name: Cache Cargo dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "cli-${{ matrix.platform.name }}" | |
| - name: Build exarch-cli | |
| run: cargo build -p exarch-cli --release --target ${{ matrix.platform.target }} | |
| - name: Package archive (Unix) | |
| if: matrix.platform.archive == 'tar.gz' | |
| run: | | |
| VERSION="${{ needs.validate.outputs.version }}" | |
| NAME="exarch-${VERSION}-${{ matrix.platform.target }}" | |
| STAGE="$(mktemp -d)/${NAME}" | |
| mkdir -p "$STAGE" | |
| cp "target/${{ matrix.platform.target }}/release/exarch" "$STAGE/" | |
| cp LICENSE-MIT LICENSE-APACHE README.md "$STAGE/" | |
| tar -C "$(dirname "$STAGE")" -czf "${NAME}.tar.gz" "${NAME}" | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| sha256sum "${NAME}.tar.gz" > "${NAME}.tar.gz.sha256" | |
| else | |
| shasum -a 256 "${NAME}.tar.gz" > "${NAME}.tar.gz.sha256" | |
| fi | |
| - name: Package archive (Windows) | |
| if: matrix.platform.archive == 'zip' | |
| shell: pwsh | |
| run: | | |
| $Version = "${{ needs.validate.outputs.version }}" | |
| $Name = "exarch-$Version-${{ matrix.platform.target }}" | |
| $Stage = Join-Path $Env:RUNNER_TEMP $Name | |
| New-Item -ItemType Directory -Path $Stage | Out-Null | |
| Copy-Item "target/${{ matrix.platform.target }}/release/exarch.exe" -Destination $Stage | |
| Copy-Item LICENSE-MIT, LICENSE-APACHE, README.md -Destination $Stage | |
| Compress-Archive -Path $Stage -DestinationPath "$Name.zip" | |
| (Get-FileHash "$Name.zip" -Algorithm SHA256).Hash.ToLower() + " $Name.zip" | Out-File -Encoding ascii "$Name.zip.sha256" | |
| - name: Upload CLI archive | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cli-${{ matrix.platform.name }} | |
| path: | | |
| exarch-*.tar.gz | |
| exarch-*.tar.gz.sha256 | |
| exarch-*.zip | |
| exarch-*.zip.sha256 | |
| retention-days: 7 | |
| # Publish Python wheels to PyPI using Trusted Publishing (OIDC) | |
| # Setup: https://docs.pypi.org/trusted-publishers/adding-a-publisher/ | |
| publish-python: | |
| name: Publish to PyPI | |
| needs: [validate, build-python-wheels] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/exarch/ | |
| permissions: | |
| id-token: write # Required for trusted publishing | |
| steps: | |
| - name: Download all wheels | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: dist | |
| pattern: wheels-* | |
| merge-multiple: true | |
| - name: List wheels | |
| run: ls -lh dist/ | |
| - name: Publish to PyPI (Trusted Publishing) | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist/ | |
| skip-existing: true | |
| verbose: true | |
| # Build Node.js native addons for multiple platforms | |
| build-node-bindings: | |
| name: Build Node.js Bindings (${{ matrix.platform.name }}) | |
| needs: validate | |
| runs-on: ${{ matrix.platform.os }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| # Linux x86_64 | |
| - name: linux-x86_64 | |
| os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| # Linux aarch64 | |
| - name: linux-aarch64 | |
| os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| # macOS x86_64 | |
| - name: macos-x86_64 | |
| os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| # macOS aarch64 (Apple Silicon) | |
| - name: macos-aarch64 | |
| os: macos-latest | |
| target: aarch64-apple-darwin | |
| # Windows x86_64 | |
| - name: windows-x86_64 | |
| os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.platform.target }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10 | |
| - name: Setup cross-compilation (Linux aarch64) | |
| if: matrix.platform.name == 'linux-aarch64' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV | |
| - name: Cache Cargo dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "node-${{ matrix.platform.name }}" | |
| - name: Install dependencies | |
| working-directory: crates/exarch-node | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Build native addon | |
| working-directory: crates/exarch-node | |
| run: pnpm run build --target ${{ matrix.platform.target }} | |
| - name: Upload bindings | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: bindings-${{ matrix.platform.name }} | |
| path: | | |
| crates/exarch-node/*.node | |
| crates/exarch-node/index.js | |
| crates/exarch-node/index.d.ts | |
| retention-days: 7 | |
| # Publish Node.js package to npm using Trusted Publishing (OIDC) | |
| # Setup: https://docs.npmjs.com/trusted-publishers/ | |
| # No NPM_TOKEN needed - uses OIDC authentication | |
| publish-node: | |
| name: Publish to npm | |
| needs: [validate, build-node-bindings] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: | |
| name: npm | |
| url: https://www.npmjs.com/package/exarch-rs | |
| permissions: | |
| contents: read | |
| id-token: write # Required for trusted publishing (OIDC) | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10 | |
| - name: Download all bindings | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: bindings | |
| pattern: bindings-* | |
| - name: Prepare npm package | |
| working-directory: crates/exarch-node | |
| run: | | |
| mkdir -p native | |
| for platform_dir in ../../bindings/bindings-*; do | |
| if [ -d "$platform_dir" ]; then | |
| cp -v "$platform_dir"/*.node native/ || true | |
| fi | |
| done | |
| ls -lh native/ | |
| - name: Install dependencies | |
| working-directory: crates/exarch-node | |
| run: pnpm install --no-frozen-lockfile | |
| - name: Publish to npm (Trusted Publishing) | |
| working-directory: crates/exarch-node | |
| run: pnpm publish --access public --no-git-checks --provenance --ignore-scripts | |
| # Create GitHub Release with artifacts | |
| github-release: | |
| name: Create GitHub Release | |
| needs: [validate, publish-crates, publish-python, publish-node, build-cli-binaries] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download Python wheels | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: release-artifacts/python | |
| pattern: wheels-* | |
| merge-multiple: true | |
| - name: Download Node.js bindings | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: release-artifacts/node | |
| pattern: bindings-* | |
| - name: Download CLI binaries | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: release-artifacts/cli | |
| pattern: cli-* | |
| merge-multiple: true | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| VERSION="${{ needs.validate.outputs.version }}" | |
| # Get the previous tag | |
| PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| echo "First release - no previous tag found" | |
| CHANGELOG="Initial release v$VERSION" | |
| else | |
| echo "Generating changelog from $PREV_TAG to v$VERSION" | |
| CHANGELOG=$(git log $PREV_TAG..HEAD --pretty=format:"- %s (%h)" --no-merges) | |
| fi | |
| # Save to file for multiline handling | |
| echo "$CHANGELOG" > changelog.txt | |
| cat >> changelog.txt << EOF | |
| ## Installation | |
| ### Rust | |
| \`\`\`toml | |
| [dependencies] | |
| exarch-core = "$VERSION" | |
| \`\`\` | |
| ### Python | |
| \`\`\`bash | |
| pip install exarch==$VERSION | |
| \`\`\` | |
| ### Node.js | |
| \`\`\`bash | |
| npm install exarch-rs@$VERSION | |
| \`\`\` | |
| ### Rust CLI | |
| \`\`\`bash | |
| cargo install exarch-cli | |
| \`\`\` | |
| Or download a prebuilt \`exarch-$VERSION-<target>\` archive from this release's assets | |
| (or run \`curl -fsSL https://raw.githubusercontent.com/bug-ops/exarch/main/scripts/install.sh | sh\`), | |
| both of which skip the Rust toolchain requirement. | |
| ## Artifacts | |
| - Python wheels for Linux (x86_64, aarch64), Linux musl (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64) | |
| - Node.js native addons for Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64) | |
| - CLI binaries for Linux (x86_64, aarch64), macOS (x86_64, aarch64), Windows (x86_64) as | |
| \`exarch-$VERSION-<target>.tar.gz\` / \`.zip\` with \`.sha256\` checksums, plus \`install.sh\` | |
| - Published to [crates.io](https://crates.io/crates/exarch-core), [PyPI](https://pypi.org/project/exarch), and [npm](https://www.npmjs.com/package/exarch-rs) | |
| EOF | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| name: Release v${{ needs.validate.outputs.version }} | |
| body_path: changelog.txt | |
| draft: false | |
| prerelease: false | |
| files: | | |
| release-artifacts/python/*.whl | |
| release-artifacts/node/**/*.node | |
| release-artifacts/cli/*.tar.gz | |
| release-artifacts/cli/*.tar.gz.sha256 | |
| release-artifacts/cli/*.zip | |
| release-artifacts/cli/*.zip.sha256 | |
| scripts/install.sh | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| # Final success check | |
| release-success: | |
| name: Release Success | |
| needs: [publish-crates, publish-python, publish-node, github-release] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Check all release jobs | |
| run: | | |
| REQUIRED_JOBS=( | |
| "${{ needs.publish-crates.result }}" | |
| "${{ needs.publish-python.result }}" | |
| "${{ needs.publish-node.result }}" | |
| "${{ needs.github-release.result }}" | |
| ) | |
| for result in "${REQUIRED_JOBS[@]}"; do | |
| if [[ "$result" != "success" ]]; then | |
| echo "Release job failed: $result" | |
| exit 1 | |
| fi | |
| done | |
| echo "All release jobs completed successfully!" |