Skip to content

Commit 854615f

Browse files
isaacroldanclaude
andcommitted
Automate tag, stable branch, and GitHub release after changeset publish
Adds five steps to the changeset-release job in release.yml that run after `pnpm release latest` succeeds: 1. Get version — reads packages/cli/package.json 2. Create tag — bare X.Y.Z, idempotent 3. Create stable branch — only on main with a minor/major bump, idempotent 4. Build release notes — from the .changeset/*.md files consumed by the Version Packages merge, with PR + author resolved by walking main's first-parent history and reading the merge subject 5. Create GitHub release — reads notes from the file written in step 4, --latest=legacy lets GitHub assign the badge by semver Release notes are scoped to changesets only — anything without a changeset is excluded by construction. Each step is independently idempotent, so re-running the workflow recovers from partial failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 362ccfa commit 854615f

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,102 @@ jobs:
116116
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117117
SHOPIFY_CLI_BUILD_REPO: ${{ github.repository }}
118118

119+
- name: Get version
120+
id: version
121+
if: steps.changesets.outputs.hasChangesets == 'false'
122+
run: |
123+
VERSION=$(node -p "require('./packages/cli/package.json').version")
124+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
125+
echo "Version: $VERSION"
126+
127+
- name: Create tag
128+
if: steps.changesets.outputs.hasChangesets == 'false'
129+
env:
130+
TAG: ${{ steps.version.outputs.version }}
131+
run: |
132+
set -euo pipefail
133+
if git ls-remote --exit-code --tags origin "$TAG" >/dev/null 2>&1; then
134+
echo "Tag $TAG already exists, skipping"
135+
exit 0
136+
fi
137+
git tag "$TAG"
138+
git push origin "$TAG"
139+
echo "Created tag $TAG"
140+
141+
- name: Create stable branch
142+
if: steps.changesets.outputs.hasChangesets == 'false' && github.ref_name == 'main' && endsWith(steps.version.outputs.version, '.0')
143+
env:
144+
VERSION: ${{ steps.version.outputs.version }}
145+
run: |
146+
set -euo pipefail
147+
MINOR=${VERSION%.0}
148+
BRANCH="stable/$MINOR"
149+
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
150+
echo "Branch $BRANCH already exists, skipping"
151+
exit 0
152+
fi
153+
git push origin "HEAD:refs/heads/$BRANCH"
154+
echo "Created branch $BRANCH"
155+
156+
- name: Build release notes
157+
if: steps.changesets.outputs.hasChangesets == 'false'
158+
env:
159+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
160+
run: |
161+
set -euo pipefail
162+
PREV=HEAD^1
163+
DELETED=$(git diff --name-only --diff-filter=D "$PREV" HEAD -- '.changeset/*.md' | grep -v 'README\.md$' || true)
164+
165+
NOTES=""
166+
while IFS= read -r FILE; do
167+
[ -z "$FILE" ] && continue
168+
CONTENT=$(git show "$PREV:$FILE")
169+
BODY=$(awk '/^---$/{c++;next} c>=2' <<< "$CONTENT" | sed '/./,$!d' | awk 'NF{p=1} p')
170+
[ -z "$BODY" ] && continue
171+
172+
ADD_SHA=$(git log --first-parent --diff-filter=A --format=%H -- "$FILE" | head -1)
173+
PR_INFO=""
174+
if [ -n "$ADD_SHA" ]; then
175+
PR_NUM=$(git log -1 --format=%s "$ADD_SHA" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
176+
if [ -n "$PR_NUM" ]; then
177+
PR_INFO=$(gh pr view "$PR_NUM" --repo "${GITHUB_REPOSITORY}" \
178+
--json url,author --jq '" by @\(.author.login) in \(.url)"' 2>/dev/null || true)
179+
fi
180+
fi
181+
182+
FIRST_LINE=$(head -n 1 <<< "$BODY")
183+
REST=$(tail -n +2 <<< "$BODY")
184+
ENTRY="- ${FIRST_LINE}${PR_INFO}"
185+
if [ -n "$REST" ]; then
186+
INDENTED_REST=$(awk '{print " "$0}' <<< "$REST")
187+
ENTRY="${ENTRY}"$'\n'"${INDENTED_REST}"
188+
fi
189+
NOTES+="${ENTRY}"$'\n\n'
190+
done <<< "$DELETED"
191+
192+
[ -z "$NOTES" ] && NOTES="_No changeset entries for this release._"
193+
194+
printf '%s' "$NOTES" > release-notes.md
195+
echo "--- Generated release notes ---"
196+
cat release-notes.md
197+
198+
- name: Create GitHub release
199+
if: steps.changesets.outputs.hasChangesets == 'false'
200+
env:
201+
TAG: ${{ steps.version.outputs.version }}
202+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
203+
run: |
204+
set -euo pipefail
205+
if gh release view "$TAG" >/dev/null 2>&1; then
206+
echo "Release $TAG already exists, skipping"
207+
exit 0
208+
fi
209+
gh release create "$TAG" \
210+
--title "$TAG" \
211+
--notes-file release-notes.md \
212+
--latest=legacy
213+
echo "Created release $TAG"
214+
119215
# Manual/Cron release job - runs on schedule or manual trigger with tag
120216
manual-cron-release:
121217
name: Manual & Cron Release

0 commit comments

Comments
 (0)