Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 72 additions & 44 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ release-cargo-publish:
cargo publish -p quicknode-cli

# Manually update the Homebrew tap with the formula attached to a given
# release. Use this until CI has a PAT with contents:write on the tap
# repo and can automate the formula push — at which point we add
# release. The tap's main branch is protected, so this commits the formula
# on a release/vX.Y.Z branch cut from the tap's latest origin/main, pushes
# it, and opens a PR. Use this until CI has a PAT with contents:write on
# the tap repo and can automate the formula push — at which point we add
# "homebrew" to publish-jobs in dist-workspace.toml, the cargo-dist
# workflow takes over, and this recipe becomes a manual-recovery fallback.
#
# Usage: just release-update-homebrew-tap 0.1.0 ~/qn/homebrew-tap
#
# Precondition: tap_path is a clean local clone of quicknode/homebrew-tap.
# Precondition: tap_path is a clean local clone of quicknode/homebrew-tap
# and `gh` is authenticated with permission to open PRs on it.
release-update-homebrew-tap version tap_path:
#!/usr/bin/env bash
set -euo pipefail
Expand All @@ -66,23 +69,32 @@ release-update-homebrew-tap version tap_path:
echo "Error: could not download $formula_url (does the release exist?)" >&2
exit 1
fi
mkdir -p "{{tap_path}}/Formula"
cp /tmp/qn.rb "{{tap_path}}/Formula/qn.rb"
rm /tmp/qn.rb
cd "{{tap_path}}"
if git diff --quiet Formula/qn.rb && ! git ls-files --error-unmatch Formula/qn.rb >/dev/null 2>&1; then
# New file
git add Formula/qn.rb
elif git diff --quiet Formula/qn.rb; then
echo "Formula/qn.rb is already at v{{version}}. Nothing to commit."
git fetch origin
branch="release/v{{version}}"
# Re-runs recreate the branch from the same base, so the later push
# needs --force-with-lease rather than a plain push.
git switch -C "$branch" origin/main
mkdir -p Formula
cp /tmp/qn.rb Formula/qn.rb
rm /tmp/qn.rb
git add Formula/qn.rb
if git diff --cached --quiet; then
echo "Formula/qn.rb is already at v{{version}} on main. Nothing to do."
exit 0
else
git add Formula/qn.rb
fi
git commit -m "qn {{version}}"
echo
echo "Committed qn {{version}} to {{tap_path}}. To publish:"
echo " git -C {{tap_path}} push"
git push --force-with-lease -u origin "$branch"
pr_url=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
if [[ -n "$pr_url" ]]; then
echo "Updated existing PR: $pr_url"
else
gh pr create \
--base main \
--head "$branch" \
--title "qn {{version}}" \
--body "Bump qn formula to v{{version}}."
fi

# Manually update the AUR `qn-bin` package with a PKGBUILD + .SRCINFO
# for a given release. Use this until CI has SSH access to push to the
Expand Down Expand Up @@ -192,9 +204,12 @@ release-update-aur-bin version aur_path:
echo " git -C {{aur_path}} push"

# Manually update the Scoop bucket with a manifest for a given release.
# Use this until CI has a PAT with contents:write on the bucket repo and
# can automate the push — at which point a CI job takes over and this
# recipe becomes a manual-recovery fallback.
# The bucket's main branch is protected, so this commits the manifest on
# a release/vX.Y.Z branch cut from the bucket's latest origin/main,
# pushes it, and opens a PR. Use this until CI has a PAT with
# contents:write on the bucket repo and can automate the publish — at
# which point a CI job takes over and this recipe becomes a
# manual-recovery fallback.
#
# The generated manifest includes `checkver` + `autoupdate` so Scoop
# users get new versions on `scoop update` even without us pushing a
Expand All @@ -203,7 +218,8 @@ release-update-aur-bin version aur_path:
#
# Usage: just release-update-scoop-bucket 0.1.4 ~/qn/scoop-bucket
#
# Precondition: bucket_path is a clean local clone of quicknode/scoop-bucket.
# Precondition: bucket_path is a clean local clone of quicknode/scoop-bucket
# and `gh` is authenticated with permission to open PRs on it.
release-update-scoop-bucket version bucket_path:
#!/usr/bin/env bash
set -euo pipefail
Expand All @@ -226,8 +242,14 @@ release-update-scoop-bucket version bucket_path:
echo "Error: parsed hash '$hash' is not a 64-char hex string." >&2
exit 1
fi
mkdir -p "{{bucket_path}}/bucket"
cat > "{{bucket_path}}/bucket/qn.json" <<EOF
cd "{{bucket_path}}"
git fetch origin
branch="release/v{{version}}"
# Re-runs recreate the branch from the same base, so the later push
# needs --force-with-lease rather than a plain push.
git switch -C "$branch" origin/main
mkdir -p bucket
cat > bucket/qn.json <<EOF
{
"version": "{{version}}",
"description": "Command-line interface for the Quicknode SDK",
Expand All @@ -250,19 +272,23 @@ release-update-scoop-bucket version bucket_path:
}
}
EOF
cd "{{bucket_path}}"
if git diff --quiet bucket/qn.json && ! git ls-files --error-unmatch bucket/qn.json >/dev/null 2>&1; then
git add bucket/qn.json
elif git diff --quiet bucket/qn.json; then
echo "bucket/qn.json is already at v{{version}}. Nothing to commit."
git add bucket/qn.json
if git diff --cached --quiet; then
echo "bucket/qn.json is already at v{{version}} on main. Nothing to do."
exit 0
else
git add bucket/qn.json
fi
git commit -m "qn {{version}}"
echo
echo "Committed qn {{version}} to {{bucket_path}}. To publish:"
echo " git -C {{bucket_path}} push"
git push --force-with-lease -u origin "$branch"
pr_url=$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty')
if [[ -n "$pr_url" ]]; then
echo "Updated existing PR: $pr_url"
else
gh pr create \
--base main \
--head "$branch" \
--title "qn {{version}}" \
--body "Bump qn manifest to v{{version}}."
fi

# Prepend a curated "How to install" section to the GitHub release body for
# v<VERSION>. The block is rendered from packaging/release-notes-install.md.tmpl
Expand Down Expand Up @@ -314,20 +340,22 @@ release-update-install-notes version mode="":
echo "Updated release v${version} with curated install block."

# Run release-update-{homebrew-tap,scoop-bucket,aur-bin} in sequence for
# the latest release tag, then print the three `git push` commands the
# maintainer needs to run to publish. Auto-detects the version from the
# most recent git tag (`v<X.Y.Z>` → `<X.Y.Z>`), or accepts an override.
# the latest release tag. The Homebrew and Scoop recipes push a release
# branch and open a PR (their main branches are protected); AUR has no
# PR flow, so its push command is printed for the maintainer to run.
# Auto-detects the version from the most recent git tag
# (`v<X.Y.Z>` → `<X.Y.Z>`), or accepts an override.
#
# Expects three sibling clones under `root` (defaults to ~/qn):
# ~/qn/homebrew-tap → quicknode/homebrew-tap
# ~/qn/scoop-bucket → quicknode/scoop-bucket
# ~/qn/qn-bin → ssh://aur@aur.archlinux.org/qn-bin.git
#
# Usage:
# just release-sync-manual-channels # auto-detect version, default root
# just release-sync-manual-channels ~/work/quicknode # override root
# just release-sync-manual-channels ~/qn 0.1.4 # override both
release-sync-manual-channels root="~/qn" version="":
# just release-finalize # auto-detect version, default root
# just release-finalize ~/work/quicknode # override root
# just release-finalize ~/qn 0.1.4 # override both
release-finalize root="~/qn" version="":
#!/usr/bin/env bash
set -euo pipefail
# Expand a leading ~/ to $HOME/ since bash doesn't expand tildes inside
Expand All @@ -343,7 +371,7 @@ release-sync-manual-channels root="~/qn" version="":
git fetch --tags --quiet
tag=$(git describe --tags --abbrev=0 2>/dev/null || true)
if [[ -z "$tag" ]]; then
echo 'Error: no git tags found and no version override passed. Run: just release-sync-manual-channels ROOT VERSION' >&2
echo 'Error: no git tags found and no version override passed. Run: just release-finalize ROOT VERSION' >&2
exit 1
fi
version="${tag#v}"
Expand All @@ -358,9 +386,9 @@ release-sync-manual-channels root="~/qn" version="":
echo
just release-update-install-notes "$version"
echo
echo "Manual channels and release-notes install block updated. To publish, run:"
echo " git -C ${root}/homebrew-tap push"
echo " git -C ${root}/scoop-bucket push"
echo "Manual channels and release-notes install block updated."
echo "Homebrew and Scoop publish via the PRs opened above — merge them to finish."
echo "AUR has no PR flow; publish it with:"
echo " git -C ${root}/qn-bin push"

# Release Phase 1: bump → branch → PR → merge → tag → GH release → wait for CI.
Expand Down Expand Up @@ -523,5 +551,5 @@ release-prepare version yes="0":
echo " https://github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner)/releases/tag/v{{version}}"
echo
echo "Next: sync the manual channels (Homebrew, Scoop, AUR) by running"
echo " just release-sync-manual-channels ~/qn {{version}}"
echo " just release-finalize ~/qn {{version}}"
echo "(omit the args to auto-detect the version from the latest tag)."
18 changes: 9 additions & 9 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ just release-prepare X.Y.Z
# release-prepare drives bump → PR → squash-merge → tag → CI through to a green run.
# Watch the workflow; come back when it's done.

just release-sync-manual-channels
just release-finalize
# Auto-detects the just-released version from the latest git tag, then bumps
# the Homebrew tap, Scoop bucket, and AUR qn-bin clones to match. Prints the
# three `git push` commands you run to publish.
# the Homebrew tap, Scoop bucket, and AUR qn-bin clones to match. Opens a PR
# against the tap and bucket repos (their main branches are protected) and
# prints the `git push` command for AUR. Merge the two PRs and run the push
# to publish.
```

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`.
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`.

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.

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

```fish
just release-update-homebrew-tap X.Y.Z ~/qn/homebrew-tap
git -C ~/qn/homebrew-tap push
```

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.
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.

### Scoop

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

```fish
just release-update-scoop-bucket X.Y.Z ~/qn/scoop-bucket
git -C ~/qn/scoop-bucket push
```

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.
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.

### AUR

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

### Curated install block on the release notes

`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:
`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:

```fish
just release-update-install-notes X.Y.Z # edits the release in place
Expand Down
Loading