-
Notifications
You must be signed in to change notification settings - Fork 4.6k
ci: add bump-homebrew-tap workflow #3355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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=<sha> DARWIN_X86=<sha> LINUX_ARM=<sha> LINUX_X86=<sha> \\ | ||
| .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]} <formula-path>", 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)) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth adding a
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| 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" | ||||||||||||||
|
cursor[bot] marked this conversation as resolved.
|
||||||||||||||
|
|
||||||||||||||
| 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 | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SHA validation count includes no-op replacements
Low Severity
re.subncounts every pattern match as a substitution even whenrepl_shareturnsmatch.group(0)unchanged (i.e., when no platform name in the URL matches theshasdict). This meansn_shareflects the number of url+sha blocks found, not the number of SHAs actually updated. The guardn_sha == 0only catches a completely missing formula structure, not the case where platform names diverge and all SHA replacements silently no-op — resulting in a committed formula with fresh version/URLs but stale checksums.Additional Locations (1)
.github/scripts/bump-homebrew-formula.py#L69-L79Reviewed by Cursor Bugbot for commit f921eb9. Configure here.