name: Release
on:
push:
tags:
- "v*"
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
verify:
name: Verify tag matches Cargo.toml version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check version
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME#v}"
CARGO_VERSION=$(sed -n '/^\[package\]/,/^\[/p' Cargo.toml \
| grep -m1 '^version' \
| sed -E 's/version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/')
echo "Tag: $TAG"
echo "Cargo.toml: $CARGO_VERSION"
if [ -z "$CARGO_VERSION" ] || [ "$TAG" != "$CARGO_VERSION" ]; then
echo "::error::Tag ($TAG) does not match Cargo.toml version ($CARGO_VERSION)"
exit 1
fi
create-release:
name: Create draft release
needs: verify
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
draft: true
generate_release_notes: true
publish-crate:
name: Publish to crates.io
needs: create-release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: publish-crate
- name: Publish browser-control
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
shell: bash
run: |
set +e
output=$(cargo publish --locked 2>&1)
rc=$?
set -e
printf '%s\n' "$output"
if [ "$rc" -eq 0 ]; then
exit 0
fi
if printf '%s' "$output" | grep -qE "already (exists|uploaded)"; then
exit 0
fi
exit "$rc"
- name: Add crates.io link to release notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
gh release edit "${GITHUB_REF_NAME}" \
--notes "$(gh release view "${GITHUB_REF_NAME}" --json body -q .body)
crates.io: https://crates.io/crates/browser-control/${VERSION}"
build-binaries:
name: Build CLI (${{ matrix.target }})
needs: create-release
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
cache-targets: false
- name: Build binary
shell: bash
run: cargo build --release --target ${{ matrix.target }} --locked
- name: Strip binary (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
BIN="target/${{ matrix.target }}/release/browser-control"
strip "$BIN" || true
- name: Package archive
shell: bash
run: |
set -euo pipefail
NAME="browser-control-${{ matrix.target }}"
STAGE="staging/$NAME"
mkdir -p "$STAGE" dist
if [ "${{ runner.os }}" = "Windows" ]; then
cp "target/${{ matrix.target }}/release/browser-control.exe" "$STAGE/"
else
cp "target/${{ matrix.target }}/release/browser-control" "$STAGE/"
fi
cp README.md LICENSE "$STAGE/"
cd staging
if [ "${{ runner.os }}" = "Windows" ]; then
7z a -tzip "../dist/${NAME}.zip" "$NAME" >/dev/null
else
tar -czf "../dist/${NAME}.tar.gz" "$NAME"
fi
- name: Upload archive to release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
shopt -s nullglob
assets=(dist/*.tar.gz dist/*.zip)
if [ "${#assets[@]}" -eq 0 ]; then
echo "::error::No release archives found under dist/"
exit 1
fi
gh release upload "${GITHUB_REF_NAME}" "${assets[@]}" --clobber
homebrew-bump:
name: Bump in-repo Homebrew formula
needs: build-binaries
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: main
- name: Download release artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p tarballs
gh release download "${GITHUB_REF_NAME}" -p "*.tar.gz" -D tarballs
- name: Compute SHAs
id: shas
shell: bash
run: |
set -euo pipefail
read_sha() {
shasum -a 256 "tarballs/browser-control-$1.tar.gz" | awk '{print $1}'
}
{
echo "darwin_arm=$(read_sha aarch64-apple-darwin)"
echo "darwin_x86=$(read_sha x86_64-apple-darwin)"
echo "linux_x86=$(read_sha x86_64-unknown-linux-gnu)"
echo "linux_arm=$(read_sha aarch64-unknown-linux-gnu)"
} >> "$GITHUB_OUTPUT"
- name: Render formula
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
BASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}"
mkdir -p Formula
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
-e "s|@@URL_DARWIN_ARM@@|${BASE_URL}/browser-control-aarch64-apple-darwin.tar.gz|g" \
-e "s|@@SHA_DARWIN_ARM@@|${{ steps.shas.outputs.darwin_arm }}|g" \
-e "s|@@URL_DARWIN_X86@@|${BASE_URL}/browser-control-x86_64-apple-darwin.tar.gz|g" \
-e "s|@@SHA_DARWIN_X86@@|${{ steps.shas.outputs.darwin_x86 }}|g" \
-e "s|@@URL_LINUX_X86@@|${BASE_URL}/browser-control-x86_64-unknown-linux-gnu.tar.gz|g" \
-e "s|@@SHA_LINUX_X86@@|${{ steps.shas.outputs.linux_x86 }}|g" \
-e "s|@@URL_LINUX_ARM@@|${BASE_URL}/browser-control-aarch64-unknown-linux-gnu.tar.gz|g" \
-e "s|@@SHA_LINUX_ARM@@|${{ steps.shas.outputs.linux_arm }}|g" \
.github/templates/browser-control.rb.tmpl \
> Formula/browser-control.rb
echo "Rendered formula:"
cat Formula/browser-control.rb
- name: Commit and push to main
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
git config --global user.email "actions@github.com"
git config --global user.name "github-actions[bot]"
git add Formula/browser-control.rb
if git diff --staged --quiet; then
echo "::notice::Formula already up to date — no commit needed"
exit 0
fi
git commit -m "release: bump Homebrew formula to ${VERSION} [skip ci]"
git push origin main
finalize-release:
name: Publish GitHub release
needs: [publish-crate, build-binaries, homebrew-bump]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Un-draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release edit "${GITHUB_REF_NAME}" \
--repo "${{ github.repository }}" \
--draft=false