Skip to content

Commit 56883b1

Browse files
dyoshikawacm-dyoshikawaclaude
authored
ci(release): auto-open Homebrew/core bump PR after npm publish (#2087)
* ci(release): auto-open Homebrew/core bump PR after npm publish Adds a homebrew job to the Publish workflow that, after the npm publish and GitHub release are finalized, derives the version from the release tag, waits for the npm tarball to propagate, computes its sha256, and runs 'brew bump-formula-pr' to open a Homebrew/core update PR. This keeps the Homebrew formula from lagging behind GitHub/npm releases. The step is gated on a maintainer-provisioned HOMEBREW_BUMP_TOKEN secret (a PAT with public_repo scope; the default GITHUB_TOKEN cannot open cross-repo PRs) and is a no-op warning when the secret is absent. Untrusted workflow_run data is passed via env vars and the tag is validated, per the project's GitHub Actions script-injection guidance. Closes #2074 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): harden Homebrew bump job (success gate, brew PATH, least privilege) Address review findings on the new homebrew job: - The custom `if:` replaced the implicit `needs: publish` success gate, so the job could still run when publish failed after the npm tarball already existed (e.g. `gh release edit` failing) — opening a Homebrew/core PR that references an unpublished draft release. Add `success()` to restore the gate. - GitHub-hosted runners ship Homebrew preinstalled but do not add it to PATH, so `brew bump-formula-pr` would fail with command-not-found. Load Homebrew's shell environment via `brew shellenv` before invoking brew. - Scope the job to `permissions: {}`; it only opens a cross-repo PR with its own HOMEBREW_BUMP_TOKEN and needs none of the workflow-level contents/id-token grants. Allowlist `linuxbrew` and `shellenv` in cspell for the new shellenv command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: dyoshikawa <yoshikawa.daio@classmethod.jp> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 66129b9 commit 56883b1

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

.github/workflows/publish.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,71 @@ jobs:
4646
exit 1
4747
fi
4848
gh release edit "$TAG" --draft=false --latest
49+
50+
homebrew:
51+
name: Bump Homebrew/core formula
52+
needs: publish
53+
runs-on: ubuntu-latest
54+
# Only bump for real releases. `success()` keeps the implicit `needs: publish`
55+
# success gate — a custom `if:` that omits it would replace that gate and let
56+
# this job run even when `publish` failed. The remaining checks mirror the
57+
# publish job's release-trigger gating.
58+
if: success() && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && startsWith(github.event.workflow_run.head_branch, 'release/v')
59+
# This job only opens a cross-repo PR with its own HOMEBREW_BUMP_TOKEN; it never
60+
# checks out this repo, uses GITHUB_TOKEN, or requests OIDC, so it needs none of
61+
# the workflow-level permissions.
62+
permissions: {}
63+
steps:
64+
- name: Open Homebrew/core bump PR
65+
env:
66+
# Maintainer-owned PAT with `public_repo` scope (forking + PRs against
67+
# Homebrew/homebrew-core). The default GITHUB_TOKEN cannot open
68+
# cross-repository PRs, so the step is skipped when this is absent.
69+
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_BUMP_TOKEN }}
70+
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
71+
run: |
72+
if [ -z "${HOMEBREW_GITHUB_API_TOKEN}" ]; then
73+
echo "::warning::HOMEBREW_BUMP_TOKEN secret is not set; skipping Homebrew formula bump."
74+
exit 0
75+
fi
76+
77+
TAG="${HEAD_BRANCH#release/}"
78+
if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
79+
echo "::error::Invalid release tag derived from branch: $HEAD_BRANCH"
80+
exit 1
81+
fi
82+
VERSION="${TAG#v}"
83+
URL="https://registry.npmjs.org/rulesync/-/rulesync-${VERSION}.tgz"
84+
85+
# Wait until the npm tarball for this version is actually downloadable
86+
# (npm registry propagation can lag behind `pnpm publish`).
87+
for attempt in $(seq 1 30); do
88+
if curl -sfIL "$URL" >/dev/null 2>&1; then
89+
break
90+
fi
91+
echo "Waiting for npm tarball to become available (attempt ${attempt}/30)..."
92+
sleep 20
93+
done
94+
if ! curl -sfIL "$URL" >/dev/null 2>&1; then
95+
echo "::error::npm tarball not available after timeout: $URL"
96+
exit 1
97+
fi
98+
99+
SHA256="$(curl -sfL "$URL" | sha256sum | cut -d' ' -f1)"
100+
if [ -z "${SHA256}" ]; then
101+
echo "::error::Failed to compute sha256 for $URL"
102+
exit 1
103+
fi
104+
105+
# GitHub-hosted runners ship Homebrew preinstalled but do not add it to
106+
# PATH, so load its shell environment before invoking brew.
107+
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
108+
109+
# Skip if Homebrew/core already has this exact version or an open bump PR
110+
# for it; brew bump-formula-pr is idempotent and will refuse duplicates.
111+
brew bump-formula-pr rulesync \
112+
--version "${VERSION}" \
113+
--url "${URL}" \
114+
--sha256 "${SHA256}" \
115+
--no-browse \
116+
--message "Automated bump from rulesync ${TAG} release."

cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
"levelname",
178178
"lightningcss",
179179
"lilconfig",
180+
"linuxbrew",
180181
"lintstagedrc",
181182
"Liskov",
182183
"listr",
@@ -274,6 +275,7 @@
274275
"saoudrizwan",
275276
"secops",
276277
"setprototypeof",
278+
"shellenv",
277279
"siginfo",
278280
"sindresorhus",
279281
"skipvalidation",

0 commit comments

Comments
 (0)