diff --git a/.github/workflows/rcli-tap.yml b/.github/workflows/rcli-tap.yml new file mode 100644 index 000000000..1aa8040c3 --- /dev/null +++ b/.github/workflows/rcli-tap.yml @@ -0,0 +1,91 @@ +name: rcli Homebrew tap + +# Keeps RunanywhereAI/homebrew-tap's Formula/rcli.rb pointing at the newest +# release. Nothing did this before, so the tap sat at 0.20.10 from 14 July while +# releases went on to 0.20.24: `brew install runanywhereai/tap/rcli` handed +# people a month-old CLI, which is what "brew install rcli doesn't work" was. +# +# This is a SEPARATE workflow on purpose. release.yml creates the GitHub Release +# as a DRAFT, and update-tap.sh reads the published .sha256 sidecars over HTTPS, +# so it cannot run inside that job — the assets are not downloadable yet. The +# `release: published` event fires when a human publishes the draft, which is +# exactly the moment the tarballs become fetchable. Keeping it out of +# release.yml also means a tap failure can never fail a release. +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "Version to point the tap at, without the leading v (e.g. 0.20.24)" + required: true + type: string + +permissions: + contents: read + +# update-tap.sh clones, commits and pushes with no rebase and no retry, so two +# runs racing (a release publish alongside a manual dispatch) can have one push +# rejected and leave the tap pointing at the older release. Queue them instead. +# cancel-in-progress stays false: a cancelled run here means a tap that never +# got updated, which is the exact failure this workflow exists to prevent. +concurrency: + group: rcli-homebrew-tap + cancel-in-progress: false + +jobs: + update-tap: + name: Point the tap at the published release + runs-on: ubuntu-latest + # Job level, not step level: a step's `if` cannot read the `secrets` context + # at all, and cannot read env declared on that same step either. Hoisting it + # here is what makes the token-presence check below actually evaluate. + env: + TAP_TOKEN: ${{ secrets.RCLI_TAP_TOKEN }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Resolve version + id: version + env: + INPUT_VERSION: ${{ inputs.version }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + version="${INPUT_VERSION:-${RELEASE_TAG#v}}" + if [ -z "${version}" ]; then + echo "::error::no version to publish" + exit 1 + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + + # The tap is a different repository, so GITHUB_TOKEN cannot write to it. + # Mirrors how the rcli signing step degrades: when the credential is + # absent, say so and stop rather than failing the run, so an unconfigured + # fork or a release cut before the secret exists stays green. + - name: Update the tap + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + if [ -z "${TAP_TOKEN}" ]; then + echo "::warning::RCLI_TAP_TOKEN not configured — skipping the Homebrew tap update." + echo "Grant a token with push access to RunanywhereAI/homebrew-tap, then re-run" + echo "this workflow, or update by hand: ./rcli/scripts/update-tap.sh ${VERSION}" + exit 0 + fi + export RCLI_TAP_REPO="https://x-access-token:${TAP_TOKEN}@github.com/RunanywhereAI/homebrew-tap.git" + ./rcli/scripts/update-tap.sh "${VERSION}" + + # Proves the formula the tap now serves actually installs and runs, on the + # same OS a user would install from. `brew test` runs the formula's own + # test block, which checks `rcli version` and `rcli backends`. + # No continue-on-error: this step exists to catch a formula that does not + # install, and a check that cannot fail would report a broken tap as + # healthy — which is how the tap sat broken for a month in the first place. + - name: Verify brew install from the tap + if: ${{ env.TAP_TOKEN != '' }} + run: | + brew tap runanywhereai/tap + brew install runanywhereai/tap/rcli + brew test runanywhereai/tap/rcli