Skip to content

Commit 876d398

Browse files
committed
Open PRs from Homebrew/Scoop release recipes; rename finalize step
The main branches of the tap and bucket repos are protected, so direct pushes are rejected. release-update-homebrew-tap and release-update-scoop-bucket now cut a release/vX.Y.Z branch from the repo's latest origin/main, commit there, push, and open a PR via gh. Re-runs recreate the branch and update the open PR. The AUR recipe is unchanged (the AUR has no PR flow). Also rename release-sync-manual-channels to release-finalize.
1 parent 6092ef4 commit 876d398

2 files changed

Lines changed: 81 additions & 53 deletions

File tree

Justfile

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ release-cargo-publish:
4242
cargo publish -p quicknode-cli
4343

4444
# Manually update the Homebrew tap with the formula attached to a given
45-
# release. Use this until CI has a PAT with contents:write on the tap
46-
# repo and can automate the formula push — at which point we add
45+
# release. The tap's main branch is protected, so this commits the formula
46+
# on a release/vX.Y.Z branch cut from the tap's latest origin/main, pushes
47+
# it, and opens a PR. Use this until CI has a PAT with contents:write on
48+
# the tap repo and can automate the formula push — at which point we add
4749
# "homebrew" to publish-jobs in dist-workspace.toml, the cargo-dist
4850
# workflow takes over, and this recipe becomes a manual-recovery fallback.
4951
#
5052
# Usage: just release-update-homebrew-tap 0.1.0 ~/qn/homebrew-tap
5153
#
52-
# Precondition: tap_path is a clean local clone of quicknode/homebrew-tap.
54+
# Precondition: tap_path is a clean local clone of quicknode/homebrew-tap
55+
# and `gh` is authenticated with permission to open PRs on it.
5356
release-update-homebrew-tap version tap_path:
5457
#!/usr/bin/env bash
5558
set -euo pipefail
@@ -66,23 +69,32 @@ release-update-homebrew-tap version tap_path:
6669
echo "Error: could not download $formula_url (does the release exist?)" >&2
6770
exit 1
6871
fi
69-
mkdir -p "{{tap_path}}/Formula"
70-
cp /tmp/qn.rb "{{tap_path}}/Formula/qn.rb"
71-
rm /tmp/qn.rb
7272
cd "{{tap_path}}"
73-
if git diff --quiet Formula/qn.rb && ! git ls-files --error-unmatch Formula/qn.rb >/dev/null 2>&1; then
74-
# New file
75-
git add Formula/qn.rb
76-
elif git diff --quiet Formula/qn.rb; then
77-
echo "Formula/qn.rb is already at v{{version}}. Nothing to commit."
73+
git fetch origin
74+
branch="release/v{{version}}"
75+
# Re-runs recreate the branch from the same base, so the later push
76+
# needs --force-with-lease rather than a plain push.
77+
git switch -C "$branch" origin/main
78+
mkdir -p Formula
79+
cp /tmp/qn.rb Formula/qn.rb
80+
rm /tmp/qn.rb
81+
git add Formula/qn.rb
82+
if git diff --cached --quiet; then
83+
echo "Formula/qn.rb is already at v{{version}} on main. Nothing to do."
7884
exit 0
79-
else
80-
git add Formula/qn.rb
8185
fi
8286
git commit -m "qn {{version}}"
83-
echo
84-
echo "Committed qn {{version}} to {{tap_path}}. To publish:"
85-
echo " git -C {{tap_path}} push"
87+
git push --force-with-lease -u origin "$branch"
88+
pr_url=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
89+
if [[ -n "$pr_url" ]]; then
90+
echo "Updated existing PR: $pr_url"
91+
else
92+
gh pr create \
93+
--base main \
94+
--head "$branch" \
95+
--title "qn {{version}}" \
96+
--body "Bump qn formula to v{{version}}."
97+
fi
8698
8799
# Manually update the AUR `qn-bin` package with a PKGBUILD + .SRCINFO
88100
# for a given release. Use this until CI has SSH access to push to the
@@ -192,9 +204,12 @@ release-update-aur-bin version aur_path:
192204
echo " git -C {{aur_path}} push"
193205
194206
# Manually update the Scoop bucket with a manifest for a given release.
195-
# Use this until CI has a PAT with contents:write on the bucket repo and
196-
# can automate the push — at which point a CI job takes over and this
197-
# recipe becomes a manual-recovery fallback.
207+
# The bucket's main branch is protected, so this commits the manifest on
208+
# a release/vX.Y.Z branch cut from the bucket's latest origin/main,
209+
# pushes it, and opens a PR. Use this until CI has a PAT with
210+
# contents:write on the bucket repo and can automate the publish — at
211+
# which point a CI job takes over and this recipe becomes a
212+
# manual-recovery fallback.
198213
#
199214
# The generated manifest includes `checkver` + `autoupdate` so Scoop
200215
# users get new versions on `scoop update` even without us pushing a
@@ -203,7 +218,8 @@ release-update-aur-bin version aur_path:
203218
#
204219
# Usage: just release-update-scoop-bucket 0.1.4 ~/qn/scoop-bucket
205220
#
206-
# Precondition: bucket_path is a clean local clone of quicknode/scoop-bucket.
221+
# Precondition: bucket_path is a clean local clone of quicknode/scoop-bucket
222+
# and `gh` is authenticated with permission to open PRs on it.
207223
release-update-scoop-bucket version bucket_path:
208224
#!/usr/bin/env bash
209225
set -euo pipefail
@@ -226,8 +242,14 @@ release-update-scoop-bucket version bucket_path:
226242
echo "Error: parsed hash '$hash' is not a 64-char hex string." >&2
227243
exit 1
228244
fi
229-
mkdir -p "{{bucket_path}}/bucket"
230-
cat > "{{bucket_path}}/bucket/qn.json" <<EOF
245+
cd "{{bucket_path}}"
246+
git fetch origin
247+
branch="release/v{{version}}"
248+
# Re-runs recreate the branch from the same base, so the later push
249+
# needs --force-with-lease rather than a plain push.
250+
git switch -C "$branch" origin/main
251+
mkdir -p bucket
252+
cat > bucket/qn.json <<EOF
231253
{
232254
"version": "{{version}}",
233255
"description": "Command-line interface for the Quicknode SDK",
@@ -250,19 +272,23 @@ release-update-scoop-bucket version bucket_path:
250272
}
251273
}
252274
EOF
253-
cd "{{bucket_path}}"
254-
if git diff --quiet bucket/qn.json && ! git ls-files --error-unmatch bucket/qn.json >/dev/null 2>&1; then
255-
git add bucket/qn.json
256-
elif git diff --quiet bucket/qn.json; then
257-
echo "bucket/qn.json is already at v{{version}}. Nothing to commit."
275+
git add bucket/qn.json
276+
if git diff --cached --quiet; then
277+
echo "bucket/qn.json is already at v{{version}} on main. Nothing to do."
258278
exit 0
259-
else
260-
git add bucket/qn.json
261279
fi
262280
git commit -m "qn {{version}}"
263-
echo
264-
echo "Committed qn {{version}} to {{bucket_path}}. To publish:"
265-
echo " git -C {{bucket_path}} push"
281+
git push --force-with-lease -u origin "$branch"
282+
pr_url=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
283+
if [[ -n "$pr_url" ]]; then
284+
echo "Updated existing PR: $pr_url"
285+
else
286+
gh pr create \
287+
--base main \
288+
--head "$branch" \
289+
--title "qn {{version}}" \
290+
--body "Bump qn manifest to v{{version}}."
291+
fi
266292
267293
# Prepend a curated "How to install" section to the GitHub release body for
268294
# v<VERSION>. The block is rendered from packaging/release-notes-install.md.tmpl
@@ -314,20 +340,22 @@ release-update-install-notes version mode="":
314340
echo "Updated release v${version} with curated install block."
315341
316342
# Run release-update-{homebrew-tap,scoop-bucket,aur-bin} in sequence for
317-
# the latest release tag, then print the three `git push` commands the
318-
# maintainer needs to run to publish. Auto-detects the version from the
319-
# most recent git tag (`v<X.Y.Z>``<X.Y.Z>`), or accepts an override.
343+
# the latest release tag. The Homebrew and Scoop recipes push a release
344+
# branch and open a PR (their main branches are protected); AUR has no
345+
# PR flow, so its push command is printed for the maintainer to run.
346+
# Auto-detects the version from the most recent git tag
347+
# (`v<X.Y.Z>``<X.Y.Z>`), or accepts an override.
320348
#
321349
# Expects three sibling clones under `root` (defaults to ~/qn):
322350
# ~/qn/homebrew-tap → quicknode/homebrew-tap
323351
# ~/qn/scoop-bucket → quicknode/scoop-bucket
324352
# ~/qn/qn-bin → ssh://aur@aur.archlinux.org/qn-bin.git
325353
#
326354
# Usage:
327-
# just release-sync-manual-channels # auto-detect version, default root
328-
# just release-sync-manual-channels ~/work/quicknode # override root
329-
# just release-sync-manual-channels ~/qn 0.1.4 # override both
330-
release-sync-manual-channels root="~/qn" version="":
355+
# just release-finalize # auto-detect version, default root
356+
# just release-finalize ~/work/quicknode # override root
357+
# just release-finalize ~/qn 0.1.4 # override both
358+
release-finalize root="~/qn" version="":
331359
#!/usr/bin/env bash
332360
set -euo pipefail
333361
# Expand a leading ~/ to $HOME/ since bash doesn't expand tildes inside
@@ -343,7 +371,7 @@ release-sync-manual-channels root="~/qn" version="":
343371
git fetch --tags --quiet
344372
tag=$(git describe --tags --abbrev=0 2>/dev/null || true)
345373
if [[ -z "$tag" ]]; then
346-
echo 'Error: no git tags found and no version override passed. Run: just release-sync-manual-channels ROOT VERSION' >&2
374+
echo 'Error: no git tags found and no version override passed. Run: just release-finalize ROOT VERSION' >&2
347375
exit 1
348376
fi
349377
version="${tag#v}"
@@ -358,9 +386,9 @@ release-sync-manual-channels root="~/qn" version="":
358386
echo
359387
just release-update-install-notes "$version"
360388
echo
361-
echo "Manual channels and release-notes install block updated. To publish, run:"
362-
echo " git -C ${root}/homebrew-tap push"
363-
echo " git -C ${root}/scoop-bucket push"
389+
echo "Manual channels and release-notes install block updated."
390+
echo "Homebrew and Scoop publish via the PRs opened above — merge them to finish."
391+
echo "AUR has no PR flow; publish it with:"
364392
echo " git -C ${root}/qn-bin push"
365393
366394
# Release Phase 1: bump → branch → PR → merge → tag → GH release → wait for CI.
@@ -523,5 +551,5 @@ release-prepare version yes="0":
523551
echo " https://github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner)/releases/tag/v{{version}}"
524552
echo
525553
echo "Next: sync the manual channels (Homebrew, Scoop, AUR) by running"
526-
echo " just release-sync-manual-channels ~/qn {{version}}"
554+
echo " just release-finalize ~/qn {{version}}"
527555
echo "(omit the args to auto-detect the version from the latest tag)."

RELEASING.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ just release-prepare X.Y.Z
1111
# release-prepare drives bump → PR → squash-merge → tag → CI through to a green run.
1212
# Watch the workflow; come back when it's done.
1313
14-
just release-sync-manual-channels
14+
just release-finalize
1515
# Auto-detects the just-released version from the latest git tag, then bumps
16-
# the Homebrew tap, Scoop bucket, and AUR qn-bin clones to match. Prints the
17-
# three `git push` commands you run to publish.
16+
# the Homebrew tap, Scoop bucket, and AUR qn-bin clones to match. Opens a PR
17+
# against the tap and bucket repos (their main branches are protected) and
18+
# prints the `git push` command for AUR. Merge the two PRs and run the push
19+
# to publish.
1820
```
1921

20-
Override the clone-root directory if your clones live elsewhere: `just release-sync-manual-channels ~/work/quicknode`. Override the version too if you're backfilling an older release: `just release-sync-manual-channels ~/qn 0.1.4`.
22+
Override the clone-root directory if your clones live elsewhere: `just release-finalize ~/work/quicknode`. Override the version too if you're backfilling an older release: `just release-finalize ~/qn 0.1.4`.
2123

2224
The rest of this document covers what each step does in detail, what to do if part of the pipeline fails, and the one-time setup for each channel.
2325

@@ -49,21 +51,19 @@ Sync the formula cargo-dist generated as a release artifact into the tap repo:
4951

5052
```fish
5153
just release-update-homebrew-tap X.Y.Z ~/qn/homebrew-tap
52-
git -C ~/qn/homebrew-tap push
5354
```
5455

55-
The recipe downloads `qn.rb` from the GitHub Release, copies it to `Formula/qn.rb` in the tap clone, commits with a clean message, and prints the push command. It does not push itself — review the diff first.
56+
The recipe downloads `qn.rb` from the GitHub Release, copies it to `Formula/qn.rb` on a `release/vX.Y.Z` branch cut from the tap's latest `origin/main`, commits, pushes the branch, and opens a PR (the tap's `main` is protected, so changes must land via PR). Review and merge the PR to publish. Re-running the recipe recreates the branch and updates the open PR.
5657

5758
### Scoop
5859

5960
Bump the canonical `version` in `bucket/qn.json`:
6061

6162
```fish
6263
just release-update-scoop-bucket X.Y.Z ~/qn/scoop-bucket
63-
git -C ~/qn/scoop-bucket push
6464
```
6565

66-
The recipe pulls the Windows zip's sha256 from the release, renders a manifest with `version`, `hash`, and an `autoupdate` block, and stages it at `bucket/qn.json`. Once a user has tapped the bucket, `scoop update` finds new versions on its own — this manual step just keeps `scoop search qn` honest about what's current.
66+
The recipe pulls the Windows zip's sha256 from the release, renders a manifest with `version`, `hash`, and an `autoupdate` block at `bucket/qn.json`, commits it on a `release/vX.Y.Z` branch cut from the bucket's latest `origin/main`, pushes the branch, and opens a PR (the bucket's `main` is protected, so changes must land via PR). Review and merge the PR to publish. Once a user has tapped the bucket, `scoop update` finds new versions on its own — this manual step just keeps `scoop search qn` honest about what's current.
6767

6868
### AUR
6969

@@ -78,7 +78,7 @@ The recipe pulls both Linux gnu sha256 sidecars (x86_64 + aarch64) from the rele
7878

7979
### Curated install block on the release notes
8080

81-
`release-sync-manual-channels` finishes by calling `release-update-install-notes`, which prepends a curated "How to install" section to the GitHub release body. Source for the block is `packaging/release-notes-install.md.tmpl`; edit it there if the install copy needs to change. The recipe is idempotent — re-running against the same release replaces the existing block rather than stacking duplicates — so it's safe to invoke standalone:
81+
`release-finalize` finishes by calling `release-update-install-notes`, which prepends a curated "How to install" section to the GitHub release body. Source for the block is `packaging/release-notes-install.md.tmpl`; edit it there if the install copy needs to change. The recipe is idempotent — re-running against the same release replaces the existing block rather than stacking duplicates — so it's safe to invoke standalone:
8282

8383
```fish
8484
just release-update-install-notes X.Y.Z # edits the release in place

0 commit comments

Comments
 (0)