Post-Release Formula Update #26
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: Post-Release Formula Update | |
| # Runs after the Release workflow finishes uploading binaries + their .sha256 | |
| # sidecars. Pulls the real shas, rewrites the formula in CorvidLabs/homebrew-tap | |
| # with the new version + shas. This is the only correct moment to bump the | |
| # formula β at `fledge release` time the new version's binaries don't exist | |
| # yet, so any pre-build sha would be a lie. | |
| # | |
| # Security note: every value derived from `github.event.workflow_run.*` (or | |
| # any other potentially attacker-controlled context) flows through an `env:` | |
| # block before reaching a shell. Direct `${{ β¦ }}` interpolation inside | |
| # `run: |` is a code-injection sink and is intentionally avoided here. | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_run: | |
| workflows: ["Release"] | |
| types: [completed] | |
| # Manual recovery lever: re-sync the formula for a given tag without | |
| # re-triggering the whole Release workflow (e.g. after rotating an expired | |
| # TAP_GITHUB_TOKEN). The workflow_run-only trigger previously left no way to | |
| # recover a failed formula update except re-running a past workflow_run. | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to sync (e.g. v1.6.0)" | |
| required: true | |
| type: string | |
| jobs: | |
| update-formula: | |
| if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'v')) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| - name: Resolve release tag | |
| id: tag | |
| env: | |
| # workflow_run supplies the tag via head_branch; manual dispatch via input. | |
| HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || github.event.inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| if ! [[ "$HEAD_BRANCH" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$ ]]; then | |
| echo "::error::Refusing to run with non-semver tag '$HEAD_BRANCH'" | |
| exit 1 | |
| fi | |
| { | |
| echo "tag=$HEAD_BRANCH" | |
| echo "version=${HEAD_BRANCH#v}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Fetch sha256 sidecars from release | |
| id: shas | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.tag.outputs.tag }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| fetch() { | |
| gh release download "$TAG" -p "$1.sha256" -R "$REPO" -O - \ | |
| | awk '{print $1}' | |
| } | |
| validate() { | |
| local sha="$1" | |
| if ! [[ "$sha" =~ ^[0-9a-fA-F]{64}$ ]]; then | |
| echo "::error::Invalid sha256 from sidecar: '$sha'" | |
| exit 1 | |
| fi | |
| printf '%s' "$sha" | |
| } | |
| { | |
| echo "macos_aarch64=$(validate "$(fetch fledge-macos-aarch64)")" | |
| echo "macos_x86_64=$(validate "$(fetch fledge-macos-x86_64)")" | |
| echo "linux_x86_64=$(validate "$(fetch fledge-linux-x86_64)")" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Sync formula to homebrew-tap | |
| env: | |
| GH_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} | |
| NEW_VERSION: ${{ steps.tag.outputs.version }} | |
| MACOS_AARCH64: ${{ steps.shas.outputs.macos_aarch64 }} | |
| MACOS_X86_64: ${{ steps.shas.outputs.macos_x86_64 }} | |
| LINUX_X86_64: ${{ steps.shas.outputs.linux_x86_64 }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$GH_TOKEN" ]; then | |
| echo "::error::TAP_GITHUB_TOKEN secret is required" | |
| exit 1 | |
| fi | |
| git clone "https://x-access-token:${GH_TOKEN}@github.com/CorvidLabs/homebrew-tap.git" /tmp/homebrew-tap | |
| cd /tmp/homebrew-tap | |
| # Rewrite the formula in-place | |
| python3 - <<'PY' | |
| import os, re, sys, pathlib | |
| p = pathlib.Path("Formula/fledge.rb") | |
| src = p.read_text() | |
| src = re.sub( | |
| r'(?m)^(\s*version\s+")(\d+\.\d+\.\d+)(")', | |
| rf'\g<1>{os.environ["NEW_VERSION"]}\g<3>', | |
| src, | |
| count=1, | |
| ) | |
| shas = [ | |
| os.environ["MACOS_AARCH64"], | |
| os.environ["MACOS_X86_64"], | |
| os.environ["LINUX_X86_64"], | |
| ] | |
| parts = re.split(r'(sha256\s+"[0-9a-fA-F]{64}")', src) | |
| if len(parts) - 1 != 6: | |
| sys.exit( | |
| f"Expected 3 sha256 lines in Formula/fledge.rb, found {(len(parts) - 1) // 2}" | |
| ) | |
| for i, sha in enumerate(shas): | |
| parts[2 * i + 1] = f'sha256 "{sha}"' | |
| p.write_text("".join(parts)) | |
| PY | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| if git diff --quiet -- Formula/fledge.rb; then | |
| echo "homebrew-tap Formula/fledge.rb already at v$NEW_VERSION β nothing to do." | |
| exit 0 | |
| fi | |
| git add Formula/fledge.rb | |
| git commit -m "chore: update formula to v$NEW_VERSION" | |
| git push origin main | |
| - name: Verify channels converged | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NEW_VERSION: ${{ steps.tag.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| # Assert the pushed formula version actually took effect. GitHub's | |
| # Contents API can serve a cached blob for several seconds after a | |
| # push, so poll with a short backoff before declaring failure β | |
| # otherwise a sync that DID land false-fails as "did not take effect" | |
| # (observed on the v1.6.0 backfill). This still turns a genuine no-op | |
| # push into a red run, just without the propagation race. | |
| tap_version="" | |
| for attempt in 1 2 3 4 5 6; do | |
| formula="$(gh api repos/CorvidLabs/homebrew-tap/contents/Formula/fledge.rb --jq '.content' | base64 --decode)" | |
| tap_version="$(printf '%s' "$formula" | sed -n 's/^[[:space:]]*version[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)" | |
| [ "$tap_version" = "$NEW_VERSION" ] && break | |
| echo "Formula still reports '$tap_version' (attempt $attempt/6); waiting for Contents API to propagate..." | |
| sleep 10 | |
| done | |
| if [ "$tap_version" != "$NEW_VERSION" ]; then | |
| echo "::error::homebrew-tap Formula/fledge.rb reports '$tap_version', expected '$NEW_VERSION' after retries β formula sync did not take effect." | |
| exit 1 | |
| fi | |
| echo "Verified homebrew-tap Formula/fledge.rb at v$NEW_VERSION." | |
| # crates.io is published out of band; warn (don't fail) if it lags so | |
| # channel drift is at least visible in the run log. | |
| crate_version="$(curl -fsSL -A 'fledge-ci' https://crates.io/api/v1/crates/fledge \ | |
| | python3 -c 'import json,sys; print(json.load(sys.stdin)["crate"]["max_version"])' 2>/dev/null || echo "")" | |
| if [ -n "$crate_version" ] && [ "$crate_version" != "$NEW_VERSION" ]; then | |
| echo "::warning::crates.io max_version '$crate_version' != release '$NEW_VERSION' β crates.io publish may be pending or missing." | |
| fi |