diff --git a/.github/scripts/bump-homebrew-formula.py b/.github/scripts/bump-homebrew-formula.py new file mode 100755 index 0000000000..ee10820408 --- /dev/null +++ b/.github/scripts/bump-homebrew-formula.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Bump version, release-tag URLs, and per-platform sha256 in a Composio Homebrew formula. + +Used by .github/workflows/cli.bump-homebrew-tap.yml. Reads inputs from env vars +so the bash side has a clean interface; can be run locally for testing: + + TAG=@composio/cli@0.2.29 VERSION=0.2.29 \\ + DARWIN_ARM= DARWIN_X86= LINUX_ARM= LINUX_X86= \\ + .github/scripts/bump-homebrew-formula.py path/to/Formula/composio.rb + +Exits non-zero if any required env var is missing/invalid or if no edits land. +""" + +from __future__ import annotations + +import os +import re +import sys + + +REQUIRED_ENV = ("TAG", "VERSION", "DARWIN_ARM", "DARWIN_X86", "LINUX_ARM", "LINUX_X86") +SHA256_RE = re.compile(r"[0-9a-f]{64}") + + +def main(argv: list[str]) -> int: + if len(argv) != 2: + print(f"usage: {argv[0]} ", file=sys.stderr) + return 2 + + path = argv[1] + missing = [k for k in REQUIRED_ENV if not os.environ.get(k)] + if missing: + print(f"missing env vars: {', '.join(missing)}", file=sys.stderr) + return 2 + + tag = os.environ["TAG"] + version = os.environ["VERSION"] + shas = { + "darwin-aarch64": os.environ["DARWIN_ARM"], + "darwin-x64": os.environ["DARWIN_X86"], + "linux-aarch64": os.environ["LINUX_ARM"], + "linux-x64": os.environ["LINUX_X86"], + } + for plat, sha in shas.items(): + if not SHA256_RE.fullmatch(sha): + print(f"invalid sha for {plat}: {sha!r}", file=sys.stderr) + return 2 + + with open(path) as f: + src = f.read() + + # Bump version line. Lambda avoids replacement-string backreference parsing + # (e.g. a hostile tag containing \g<1>); same defense on tag/sha below. + src, n_ver = re.subn( + r'^(\s*version\s+)"[^"]+"', + lambda m: f'{m.group(1)}"{version}"', + src, + count=1, + flags=re.M, + ) + + # Bump the release-tag segment of each download URL. + src, n_url = re.subn( + r"(releases/download/)@composio/cli@[^/]+(/composio-)", + lambda m: f"{m.group(1)}{tag}{m.group(2)}", + src, + ) + + # Bump each sha256 by pairing it with the URL line directly above. + def repl_sha(match: re.Match[str]) -> str: + url_line, sha_line = match.group(1), match.group(2) + for plat, sha in shas.items(): + if f"composio-{plat}.zip" in url_line: + return url_line + re.sub( + r'"[0-9a-f]{64}"', + lambda _m: f'"{sha}"', + sha_line, + ) + return match.group(0) + + src, n_sha = re.subn( + r'(url\s+"[^"]+"\s*\n)(\s*sha256\s+"[0-9a-f]{64}")', + repl_sha, + src, + ) + + if n_ver != 1 or n_url == 0 or n_sha == 0: + print( + f"bump produced unexpected edit counts (version={n_ver}, url={n_url}, sha={n_sha})", + file=sys.stderr, + ) + return 1 + + with open(path, "w") as f: + f.write(src) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/.github/workflows/cli.bump-homebrew-tap.yml b/.github/workflows/cli.bump-homebrew-tap.yml new file mode 100644 index 0000000000..aa8c2e26a4 --- /dev/null +++ b/.github/workflows/cli.bump-homebrew-tap.yml @@ -0,0 +1,120 @@ +name: Bump Homebrew Tap + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to sync (e.g. @composio/cli@0.2.28)' + required: true + type: string + +permissions: + contents: read + +concurrency: + group: bump-homebrew-tap + cancel-in-progress: false + +jobs: + bump: + if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '@composio/cli@') + runs-on: ubuntu-latest + steps: + - name: Resolve release tag and version + id: meta + env: + TAG_INPUT: ${{ inputs.tag }} + TAG_RELEASE: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + tag="${TAG_INPUT:-$TAG_RELEASE}" + if [[ -z "$tag" ]]; then + echo "::error::No tag resolved"; exit 1 + fi + if [[ ! "$tag" =~ ^@composio/cli@ ]]; then + echo "::notice::Tag $tag is not a CLI release, skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + # Skip beta/prerelease tags — only stable bumps the formula. + if [[ "$tag" == *"-beta."* || "$tag" == *"-rc."* || "$tag" == *"-alpha."* ]]; then + echo "::notice::Tag $tag is a prerelease, skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + version="${tag#@composio/cli@}" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Download checksums + if: steps.meta.outputs.skip != 'true' + id: sha + env: + TAG: ${{ steps.meta.outputs.tag }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + gh release download "$TAG" \ + --repo "${{ github.repository }}" \ + --pattern checksums.txt \ + --output checksums.txt + extract() { + grep " $1\$" checksums.txt | awk '{print $1}' || true + } + { + echo "darwin_arm=$(extract composio-darwin-aarch64.zip)" + echo "darwin_x86=$(extract composio-darwin-x64.zip)" + echo "linux_arm=$(extract composio-linux-aarch64.zip)" + echo "linux_x86=$(extract composio-linux-x64.zip)" + } >> "$GITHUB_OUTPUT" + + - name: Checkout source repo (for bump script) + if: steps.meta.outputs.skip != 'true' + uses: actions/checkout@v4 + + - name: Checkout homebrew tap + if: steps.meta.outputs.skip != 'true' + uses: actions/checkout@v4 + with: + repository: ComposioHQ/homebrew-tap + token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + path: tap + + - name: Update formula + if: steps.meta.outputs.skip != 'true' + env: + VERSION: ${{ steps.meta.outputs.version }} + TAG: ${{ steps.meta.outputs.tag }} + DARWIN_ARM: ${{ steps.sha.outputs.darwin_arm }} + DARWIN_X86: ${{ steps.sha.outputs.darwin_x86 }} + LINUX_ARM: ${{ steps.sha.outputs.linux_arm }} + LINUX_X86: ${{ steps.sha.outputs.linux_x86 }} + run: | + set -euo pipefail + formula="tap/Formula/composio.rb" + [[ -f "$formula" ]] || { echo "::error::$formula not found"; exit 1; } + + .github/scripts/bump-homebrew-formula.py "$formula" + + echo "--- updated formula ---" + cat "$formula" + + - name: Commit and push + if: steps.meta.outputs.skip != 'true' + working-directory: tap + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + git config user.name "composio-release-bot" + git config user.email "composio-release-bot@users.noreply.github.com" + if git diff --quiet; then + echo "Formula already up to date for $VERSION; nothing to commit." + exit 0 + fi + git add Formula/composio.rb + git commit -m "composio $VERSION" + git push origin HEAD