jetstreamer 0.7.0

High-throughput Solana transaction ledger streaming and plugin framework suitable for research and backfilling
Documentation
name: Release

# Releases the entire workspace in dependency order with a single dispatch and a
# single prod approval: jetstreamer-utils -> jetstreamer-firehose ->
# jetstreamer-plugin -> jetstreamer. For each crate the three effects — crates.io
# publish, crate@vX.Y.Z tag, GitHub release — are checked and performed
# independently, so an interrupted release can be resumed by re-running the
# workflow and only the missing effects are performed. Versions are bumped
# workspace-wide in a PR before dispatching.

on:
  workflow_dispatch:
    inputs:
      ref:
        description: "git ref to release (can be a branch or a commit hash)"
        required: true
        default: "main"
        type: string

jobs:
  check:
    runs-on: ubuntu-latest
    outputs:
      ref: ${{ steps.plan.outputs.ref }}
      first: ${{ steps.plan.outputs.first }}
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false

      - name: Dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential libudev-dev clang llvm-dev libclang-dev pkg-config libssl-dev

      - name: Compute release plan
        id: plan
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          first=""
          work=false
          echo "## Release plan" >> "${GITHUB_STEP_SUMMARY}"
          for crate in jetstreamer-utils jetstreamer-firehose jetstreamer-plugin jetstreamer; do
            version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "'"${crate}"'") | .version')"
            if [ -z "${version}" ] || [ "${version}" = "null" ]; then
              echo "Could not resolve version for crate: ${crate}"
              exit 1
            fi
            tag="${crate}@v${version}"
            todo=""
            if ! curl -fsSL --retry 3 -H "User-Agent: jetstreamer-release-workflow (github.com/anza-xyz/jetstreamer)" \
              "https://crates.io/api/v1/crates/${crate}/${version}" 2>/dev/null | jq -e '.version.num == "'"${version}"'"' >/dev/null 2>&1; then
              todo="publish"
              if [ -z "${first}" ]; then
                first="${crate}"
              fi
            fi
            if ! git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
              todo="${todo:+${todo}, }tag"
            fi
            if ! gh release view "${tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
              todo="${todo:+${todo}, }github release"
            fi
            if [ -n "${todo}" ]; then
              work=true
              echo "- \`${tag}\`: needs ${todo}" >> "${GITHUB_STEP_SUMMARY}"
            else
              echo "- \`${tag}\`: fully released, will skip" >> "${GITHUB_STEP_SUMMARY}"
            fi
          done
          if [ "${work}" = "false" ]; then
            echo "Every crate at the current version is already fully released. Bump versions first."
            exit 1
          fi
          echo "first=${first}" >> "${GITHUB_OUTPUT}"
          echo "ref=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"

      # Because versions bump workspace-wide and publishing runs in dependency
      # order, the first unpublished crate's workspace dependencies are always
      # already on crates.io — so it is always dry-runnable. Later crates depend
      # on unpublished versions and are validated by `cargo publish` itself.
      # Skipped entirely when only tags/releases are missing (nothing to publish).
      - name: Cargo publish dry run (first unpublished crate)
        if: steps.plan.outputs.first != ''
        run: cargo publish -p "${FIRST}" --dry-run
        env:
          FIRST: ${{ steps.plan.outputs.first }}

  publish:
    runs-on: ubuntu-latest
    environment: prod
    needs: check
    permissions:
      id-token: write
      contents: write
      attestations: write
      artifact-metadata: write
    steps:
      - name: Create github app token
        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
        id: app-token
        with:
          client-id: ${{ vars.CLIENT_ID }}
          private-key: ${{ secrets.PRIVATE_KEY }}

      - name: Set git config
        run: |
          git config --global user.email "${APP_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
          git config --global user.name "${APP_SLUG}[bot]"
          git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf https://github.com/
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
          APP_ID: ${{ vars.APP_ID }}
          APP_SLUG: ${{ steps.app-token.outputs.app-slug }}

      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          token: ${{ steps.app-token.outputs.token }}
          persist-credentials: false
          ref: ${{ needs.check.outputs.ref }}

      - name: Dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential libudev-dev clang llvm-dev libclang-dev pkg-config libssl-dev

      # Each crate gets a freshly minted crates.io token immediately before its
      # publish: verification builds are long enough that a single token could
      # expire before the last crate publishes.
      # Provenance is attested per crate, immediately after its publish and only
      # when THIS run performed the publish — so a partially-failed release never
      # leaves a published crate unattested, and reruns never re-attest crates
      # they merely skipped.
      - name: Create crate token (jetstreamer-utils)
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
        id: auth-utils
      - name: Release jetstreamer-utils
        id: release-utils
        run: .github/scripts/release-crate.sh jetstreamer-utils
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth-utils.outputs.token }}
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
      - name: Generate SLSA provenance (jetstreamer-utils)
        if: steps.release-utils.outputs.published == 'true'
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        with:
          subject-path: target/package/jetstreamer-utils-*.crate

      - name: Create crate token (jetstreamer-firehose)
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
        id: auth-firehose
      - name: Release jetstreamer-firehose
        id: release-firehose
        run: .github/scripts/release-crate.sh jetstreamer-firehose
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth-firehose.outputs.token }}
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
      - name: Generate SLSA provenance (jetstreamer-firehose)
        if: steps.release-firehose.outputs.published == 'true'
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        with:
          subject-path: target/package/jetstreamer-firehose-*.crate

      - name: Create crate token (jetstreamer-plugin)
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
        id: auth-plugin
      - name: Release jetstreamer-plugin
        id: release-plugin
        run: .github/scripts/release-crate.sh jetstreamer-plugin
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth-plugin.outputs.token }}
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
      - name: Generate SLSA provenance (jetstreamer-plugin)
        if: steps.release-plugin.outputs.published == 'true'
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        with:
          subject-path: target/package/jetstreamer-plugin-*.crate

      - name: Create crate token (jetstreamer)
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
        id: auth-jetstreamer
      - name: Release jetstreamer
        id: release-jetstreamer
        run: .github/scripts/release-crate.sh jetstreamer
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth-jetstreamer.outputs.token }}
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
      - name: Generate SLSA provenance (jetstreamer)
        if: steps.release-jetstreamer.outputs.published == 'true'
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        with:
          # [0-9] keeps the glob from also matching jetstreamer-{utils,...}
          subject-path: target/package/jetstreamer-[0-9]*.crate