netspeed-cli 0.9.0

Command-line interface for testing internet bandwidth using speedtest.net
Documentation
name: Release

on:
  push:
    tags:
      - 'v*'

env:
  CARGO_TERM_COLOR: always

jobs:
  verify-tag-on-main:
    name: Verify Tag is on Main
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: Verify tag points to main branch
        run: |
          if ! git branch -r --contains "${{ github.ref_name }}" | grep -q 'origin/main'; then
            echo "::error::Tag ${{ github.ref_name }} is not on the 'main' branch."
            echo "::error::Releases must be created from main. Merge develop → main first."
            echo "::error::See RELEASE.md for the release process."
            exit 1
          fi
          echo "Tag ${{ github.ref_name }} is on main branch"

  build-binaries:
    name: Build Binary (${{ matrix.target }})
    runs-on: ${{ matrix.os }}
    needs: [verify-tag-on-main]
    strategy:
      matrix:
        include:
          # Linux GNU (glibc)
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-x86_64-linux-gnu
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-aarch64-linux-gnu
          # Linux musl (static)
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-x86_64-linux-musl
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-aarch64-linux-musl
          # macOS
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-x86_64-macos
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact_name: netspeed-cli
            asset_name: netspeed-cli-aarch64-macos
          # Windows
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            artifact_name: netspeed-cli.exe
            asset_name: netspeed-cli-x86_64-windows.exe
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - name: Cache dependencies
        uses: Swatinem/rust-cache@v2
      - name: Install cross-compilation tools (aarch64 Linux GNU)
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
      - name: Configure aarch64 linker
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          mkdir -p .cargo
          cat > .cargo/config.toml << 'EOF'
          [target.aarch64-unknown-linux-gnu]
          linker = "aarch64-linux-gnu-gcc"
          EOF
      - name: Install musl tools (x86_64)
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools
      - name: Install cross (aarch64 musl)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: cargo install cross --locked
      - name: Build release (cross)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: cross build --release --target ${{ matrix.target }}
      - name: Build release (native)
        if: matrix.target != 'aarch64-unknown-linux-musl'
        run: cargo build --release --target ${{ matrix.target }}
      - name: Prepare artifact
        shell: bash
        run: |
          mkdir -p dist
          cp "target/${{ matrix.target }}/release/${{ matrix.artifact_name }}" dist/
      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: ${{ matrix.asset_name }}
          path: dist/${{ matrix.artifact_name }}

  publish-github-release:
    name: Create GitHub Release
    runs-on: ubuntu-latest
    needs: [build-binaries]
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: actions/download-artifact@v8
        with:
          path: release-assets
          merge-multiple: true
      - name: Generate SHA256 checksums
        run: |
          cd release-assets
          sha256sum * > SHA256SUMS.txt
          cat SHA256SUMS.txt
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          path: .
          artifact-name: sbom.spdx.json
          output-file: release-assets/sbom.spdx.json
          format: spdx-json
      - name: Install git-cliff for changelog
        run: cargo install git-cliff --locked
      - name: Update CHANGELOG.md for release
        run: |
          echo "Generating release changelog..."
          git-cliff --config .cliff.toml --changelog CHANGELOG.md
          echo "Changelog generated"
          cat CHANGELOG.md | head -50
      - name: Create GitHub Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Read changelog for release notes
          CHANGELOG_CONTENT=$(cat CHANGELOG.md)
          
          gh release delete "${{ github.ref_name }}" --yes --repo "${{ github.repository }}" 2>/dev/null || true
          gh release create "${{ github.ref_name }}" \
            --title "${{ github.ref_name }}" \
            --notes "$CHANGELOG_CONTENT" \
            --repo "${{ github.repository }}"
      - name: Upload assets to release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload "${{ github.ref_name }}" \
            release-assets/* \
            --repo "${{ github.repository }}" \
            --clobber
      - name: Update Homebrew formula
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail

          VERSION="${{ github.ref_name }}"
          VERSION_NO_V="${VERSION#v}"

          # Download tarball and calculate SHA256 with error handling
          echo "Downloading tarball to calculate SHA256..."
          if ! SHA256=$(curl -sfL "https://github.com/${{ github.repository }}/archive/refs/tags/${VERSION}.tar.gz" | sha256sum | awk '{print $1}'); then
            echo "::error::Failed to download tarball or calculate SHA256"
            exit 1
          fi

          if [ -z "$SHA256" ]; then
            echo "::error::SHA256 is empty - download may have failed"
            exit 1
          fi
          echo "SHA256: $SHA256"

          # Update formula file with robust sed patterns
          sed -i "s/version \".*\"/version \"${VERSION_NO_V}\"/" netspeed-cli.rb
          sed -i "s|url \".*\"|url \"https://github.com/${{ github.repository }}/archive/refs/tags/${VERSION}.tar.gz\"|" netspeed-cli.rb
          sed -i "s/sha256 \".*\"/sha256 \"${SHA256}\"/" netspeed-cli.rb

          # Verify all updates were applied correctly
          echo "Verifying formula updates..."
          if ! grep -q "version \"${VERSION_NO_V}\"" netspeed-cli.rb; then
            echo "::error::Version not updated correctly"
            cat netspeed-cli.rb
            exit 1
          fi

          if ! grep -q "archive/refs/tags/${VERSION}.tar.gz\"$" netspeed-cli.rb; then
            echo "::error::URL not updated correctly"
            cat netspeed-cli.rb
            exit 1
          fi

          if ! grep -q "sha256 \"$SHA256\"" netspeed-cli.rb; then
            echo "::error::SHA256 not updated correctly"
            cat netspeed-cli.rb
            exit 1
          fi

          echo "Formula update verified successfully"
          echo "Updated formula:"
          cat netspeed-cli.rb

          # Commit and push to main branch
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add netspeed-cli.rb
          git commit -m "chore: update formula to ${VERSION}"
          git push origin HEAD:main

  publish-crates-io:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: [publish-github-release]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - name: Cache dependencies
        uses: Swatinem/rust-cache@v2
      - name: Verify tests pass
        run: |
          cargo test --verbose
          cargo test --doc
          cargo package --locked
      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish --locked