From 4925ab218ec12d42854e32bc2cd1adf917b6cebb Mon Sep 17 00:00:00 2001 From: MatthewHsu1 Date: Mon, 24 Aug 2026 11:30:38 -0600 Subject: [PATCH] chore: make just release open a pull request main is protected and refuses direct pushes, so the old recipe always failed at 'git push origin main' after it had already committed and tagged locally. The flow is now two steps: just release bumps the version on chore/release- and opens the pull request. Tags nothing. just release-tag after the merge, tags main and pushes the tag, which is what .github/workflows/publish.yml listens for. The tag is deliberately made after the merge. A tag made before it would point at a commit that a squash or rebase merge discards, and a published version cannot be taken back. --- justfile | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/justfile b/justfile index 5ceb60e..c229f99 100644 --- a/justfile +++ b/justfile @@ -22,7 +22,8 @@ status: echo "newest tag : $tag" echo "on npm : $published" -# Publish a new version. LEVEL is patch, minor, or major. +# Step 1 of 2. Bump the version and open the release pull request. +# LEVEL is patch, minor, or major. This tags nothing. release level="patch": #!/usr/bin/env bash set -euo pipefail @@ -32,8 +33,13 @@ release level="patch": *) echo "error: level must be patch, minor, or major"; exit 1 ;; esac - # A tag is only correct if it points at code that is already on GitHub. - # Everything below refuses to tag until that is true. + if ! command -v gh >/dev/null 2>&1; then + echo "error: the GitHub CLI is missing. Install it from https://cli.github.com" + exit 1 + fi + + # main is protected: it only takes commits through a pull request. So the + # bump is made on its own branch and merged the normal way. branch="$(git rev-parse --abbrev-ref HEAD)" if [ "$branch" != "main" ]; then echo "error: you are on '$branch'. Switch to main first." @@ -52,15 +58,82 @@ release level="patch": exit 1 fi - # Never tag code that does not build. A bad tag cannot be unpublished. + # Never open a release pull request for code that does not build. just check - # npm version bumps package.json, commits, and tags -- in that order, - # on the newest commit. Do not do these steps by hand. - new="$(npm version {{level}} -m 'chore: release %s')" + # --no-git-tag-version: bump package.json and the lockfile, nothing else, + # and print the new version. The tag is made later by `just release-tag`, + # once the merged commit exists. A tag made now would point at a commit + # that a squash or rebase merge throws away, and a wrong tag cannot be + # unpublished. + tag="$(npm version {{level}} --no-git-tag-version)" + version="${tag#v}" + release_branch="chore/release-$version" + + if git rev-parse -q --verify "refs/heads/$release_branch" >/dev/null; then + git checkout -- package.json package-lock.json + echo "error: branch $release_branch already exists. Finish or delete it first." + exit 1 + fi + + # The bumped files are uncommitted, so they follow us onto the new branch + # and main is left exactly as it was. + git switch --create "$release_branch" + git commit --all --message "chore: release $version" + git push --set-upstream origin "$release_branch" + + body="$(printf '%s\n\n%s\n' \ + "Version bump to $version, made by \`just release {{level}}\`." \ + "Merging this does not publish anything. After it merges, run \`git switch main && just release-tag\` to tag $tag, which is what starts the npm publish.")" + + gh pr create --base main --title "chore: release $version" --body "$body" + + echo "" + echo "opened the pull request for $version. Merging it does NOT publish." + echo "next: merge it, then run git switch main && just release-tag" + +# Step 2 of 2. Tag the merged release and start the npm publish. +release-tag: + #!/usr/bin/env bash + set -euo pipefail + + branch="$(git rev-parse --abbrev-ref HEAD)" + if [ "$branch" != "main" ]; then + echo "error: you are on '$branch'. Run: git switch main" + exit 1 + fi + + if [ -n "$(git status --porcelain)" ]; then + echo "error: you have uncommitted changes. Commit or stash them first." + git status --short + exit 1 + fi + + git fetch origin --prune --tags + git merge --ff-only origin/main + + version="$(node -p "require('./package.json').version")" + tag="v$version" + + if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then + # A tag left over from an earlier run. It is only correct if the code it + # points at is the code that is now on main. + if ! git merge-base --is-ancestor "$tag" origin/main; then + echo "error: tag $tag exists but its commit is not on main." + echo "delete it and run this again: git tag --delete $tag" + exit 1 + fi + echo "reusing the existing tag $tag." + else + git tag --annotate "$tag" --message "chore: release $version" + fi + + if git ls-remote --exit-code --tags origin "$tag" >/dev/null 2>&1; then + echo "error: $tag is already on GitHub. This release is already done." + exit 1 + fi - git push origin main - git push origin "$new" + git push origin "$tag" echo "" - echo "pushed $new. GitHub Actions is publishing it now." + echo "pushed $tag. GitHub Actions is publishing it now."