braze-sync 0.9.0

GitOps CLI for managing Braze configuration as code
Documentation
name: Release

on:
  push:
    tags: ["v*"]

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always

# Third-party actions are pinned by commit SHA (with a trailing
# version comment) per GitHub's hardening guidance. See ci.yml for
# the bump procedure.
jobs:
  # Run the full test suite before building release artifacts.
  ci:
    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
      - run: cargo test --all-features
      - run: cargo clippy --all-targets --all-features -- -D warnings
        if: matrix.os == 'ubuntu-latest'

  build:
    needs: ci
    strategy:
      fail-fast: true
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
          - target: aarch64-unknown-linux-musl
            os: ubuntu-latest
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          key: ${{ matrix.target }}

      # musl toolchain for fully static Linux binaries. x86_64-musl is
      # provided by the image; aarch64 needs an explicit cross-linker.
      - name: Install musl tooling (x86_64)
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools
          echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc" >> "$GITHUB_ENV"

      - name: Install musl tooling (aarch64)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools gcc-aarch64-linux-gnu
          echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
          echo "CC_aarch64_unknown_linux_musl=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"

      - name: Build release binary
        run: cargo build --release --locked --target ${{ matrix.target }}

      - name: Package (unix)
        if: runner.os != 'Windows'
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../braze-sync-${{ matrix.target }}.tar.gz braze-sync
          cd ../../..
          shasum -a 256 braze-sync-${{ matrix.target }}.tar.gz > braze-sync-${{ matrix.target }}.tar.gz.sha256

      - name: Package (windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          cd target/${{ matrix.target }}/release
          Compress-Archive -Path braze-sync.exe -DestinationPath ../../../braze-sync-${{ matrix.target }}.zip
          cd ../../..
          (Get-FileHash braze-sync-${{ matrix.target }}.zip -Algorithm SHA256).Hash.ToLower() + "  braze-sync-${{ matrix.target }}.zip" | Out-File -Encoding ascii braze-sync-${{ matrix.target }}.zip.sha256

      - name: Upload artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: braze-sync-${{ matrix.target }}
          path: braze-sync-${{ matrix.target }}.*

  release:
    needs: build
    runs-on: ubuntu-latest
    # `id-token: write` lets cosign exchange a short-lived OIDC token
    # with Sigstore's Fulcio CA for keyless signing. `contents: write`
    # is required to create the GitHub Release.
    permissions:
      contents: write
      id-token: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: artifacts
          merge-multiple: true

      - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0

      # Sigstore keyless signing. See README → "Verifying release artifacts".
      - name: Sign release artifacts (cosign keyless)
        env:
          COSIGN_YES: "true"
        run: |
          set -euo pipefail
          for f in artifacts/braze-sync-*.tar.gz artifacts/braze-sync-*.zip; do
            cosign sign-blob --bundle "${f}.cosign.bundle" "${f}"
          done

      - name: Create GitHub Release
        uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
        with:
          generate_release_notes: true
          files: artifacts/*

  # Runs after `release` so a publish failure leaves binaries uploaded
  # and `cargo publish` can be retried manually.
  publish:
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
      - run: cargo publish --locked
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  # Updates uny/homebrew-tap/Formula/braze-sync.rb to point at the new
  # release artifacts. Runs only on stable tags (no pre-release suffix)
  # so `brew install` never picks up an RC. Uses a GitHub App
  # (uny-release-bot) to mint a short-lived installation token scoped
  # to the tap repository — avoids long-lived PATs.
  homebrew:
    needs: release
    # Skip pre-release tags like v0.9.0-rc.1. `contains('-')` is a
    # simple gate that matches every semver pre-release identifier.
    if: ${{ !contains(github.ref_name, '-') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: artifacts
          merge-multiple: true

      - name: Compute SHA256 for each target
        id: sha
        run: |
          set -euo pipefail
          cd artifacts
          # release.yml generates "<sha>  <filename>" sidecar files.
          sha_of() {
            awk '{print $1}' "braze-sync-$1.tar.gz.sha256"
          }
          {
            echo "mac_arm=$(sha_of aarch64-apple-darwin)"
            echo "mac_intel=$(sha_of x86_64-apple-darwin)"
            echo "linux_arm=$(sha_of aarch64-unknown-linux-musl)"
            echo "linux_intel=$(sha_of x86_64-unknown-linux-musl)"
          } >> "$GITHUB_OUTPUT"

      - name: Mint tap token (uny-release-bot App)
        id: app-token
        uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
        with:
          app-id: ${{ secrets.UNY_RELEASE_BOT_APP_ID }}
          private-key: ${{ secrets.UNY_RELEASE_BOT_PRIVATE_KEY }}
          owner: uny
          repositories: homebrew-tap

      - name: Checkout tap
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          repository: uny/homebrew-tap
          token: ${{ steps.app-token.outputs.token }}
          path: tap

      - name: Write Formula
        env:
          VERSION: ${{ github.ref_name }}
          MAC_ARM: ${{ steps.sha.outputs.mac_arm }}
          MAC_INTEL: ${{ steps.sha.outputs.mac_intel }}
          LINUX_ARM: ${{ steps.sha.outputs.linux_arm }}
          LINUX_INTEL: ${{ steps.sha.outputs.linux_intel }}
        run: |
          set -euo pipefail
          v="${VERSION#v}"
          cat > tap/Formula/braze-sync.rb <<EOF
          class BrazeSync < Formula
            desc "GitOps CLI for managing Braze configuration as code"
            homepage "https://github.com/uny/braze-sync"
            version "${v}"
            license "MIT"

            on_macos do
              on_arm do
                url "https://github.com/uny/braze-sync/releases/download/v#{version}/braze-sync-aarch64-apple-darwin.tar.gz"
                sha256 "${MAC_ARM}"
              end

              on_intel do
                url "https://github.com/uny/braze-sync/releases/download/v#{version}/braze-sync-x86_64-apple-darwin.tar.gz"
                sha256 "${MAC_INTEL}"
              end
            end

            on_linux do
              on_arm do
                url "https://github.com/uny/braze-sync/releases/download/v#{version}/braze-sync-aarch64-unknown-linux-musl.tar.gz"
                sha256 "${LINUX_ARM}"
              end

              on_intel do
                url "https://github.com/uny/braze-sync/releases/download/v#{version}/braze-sync-x86_64-unknown-linux-musl.tar.gz"
                sha256 "${LINUX_INTEL}"
              end
            end

            def install
              bin.install "braze-sync"
            end

            test do
              assert_match version.to_s, shell_output("#{bin}/braze-sync --version")
            end
          end
          EOF

      - name: Commit and push
        working-directory: tap
        env:
          VERSION: ${{ github.ref_name }}
          APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
          APP_USER_ID: ${{ steps.app-token.outputs.user-id }}
        run: |
          set -euo pipefail
          git config user.name "${APP_SLUG}[bot]"
          # Numeric user-id is required for GitHub to attribute the
          # commit to the App's bot user (and mark it Verified); it
          # also survives an App rename.
          git config user.email "${APP_USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
          git add Formula/braze-sync.rb
          if git diff --cached --quiet; then
            echo "Formula already up to date; nothing to commit."
            exit 0
          fi
          git commit -m "braze-sync ${VERSION}"
          git push