Skip to content

Commit ad42f29

Browse files
rubnogueiraclaude
andcommitted
feat(ci): auto-tag and release on version bump to master (v2.4.3)
Previously a release required pushing a `v*` tag manually, and a separate gate-release job verified the tag was reachable from master. Drop that whole flow in favour of a simpler model: 1. PR is opened: matrix runs for verification only. 2. PR merges to master: matrix runs again. If every job succeeds AND the package.json version on the merge commit has no matching `v<version>` tag on origin yet, the release job: - creates the tag pointing at the merge commit - creates a GitHub Release named after the tag - uploads the prebuild tarballs as release assets The tag is created by softprops/action-gh-release using the built-in GITHUB_TOKEN, so it does not cascade back into the workflow. 3. Master push without a version bump: matrix runs, release step sees the tag already exists, skips. No-op release. Cutting the first release of this flow by bumping to 2.4.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fc240c4 commit ad42f29

3 files changed

Lines changed: 42 additions & 36 deletions

File tree

.github/workflows/prebuild.yml

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ name: prebuild
33
on:
44
push:
55
branches: [master, main]
6-
tags: ['v*']
76
pull_request:
87
workflow_dispatch:
98

@@ -135,59 +134,66 @@ jobs:
135134
if-no-files-found: error
136135
retention-days: 30
137136

138-
# Gate the release on tag being reachable from master. If the tag was
139-
# pushed from a feature branch (or any non-master commit), this job
140-
# outputs on_master=false and the release job is skipped (not failed).
141-
gate-release:
142-
name: gate release on master ancestry
137+
# Release flow: only fires on push to master, only after every matrix
138+
# job succeeded, and only when the package.json version on the new
139+
# commit does not yet have a matching `v<version>` tag on origin.
140+
# That makes the release a function of "merged version bump to master"
141+
# — no manual tagging required.
142+
release:
143+
name: auto-release on version bump
143144
needs: prebuild
144-
if: startsWith(github.ref, 'refs/tags/v')
145+
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')
145146
runs-on: ubuntu-latest
146-
outputs:
147-
on_master: ${{ steps.check.outputs.on_master }}
147+
permissions:
148+
contents: write
148149
steps:
149150
- uses: actions/checkout@v5
150-
with:
151-
fetch-depth: 0
152-
- id: check
151+
152+
- name: Read version from package.json
153+
id: version
154+
run: |
155+
set -euo pipefail
156+
v=$(node -p "require('./package.json').version")
157+
echo "version=$v" >> "$GITHUB_OUTPUT"
158+
echo "tag=v$v" >> "$GITHUB_OUTPUT"
159+
echo "Detected package version: $v (target tag: v$v)"
160+
161+
- name: Check whether tag already exists on origin
162+
id: check_tag
153163
run: |
154164
set -euo pipefail
155-
git fetch origin master
156-
if git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then
157-
echo "Tag $GITHUB_REF_NAME (commit $GITHUB_SHA) is on master — release will publish"
158-
echo "on_master=true" >> "$GITHUB_OUTPUT"
165+
tag="${{ steps.version.outputs.tag }}"
166+
if git ls-remote --tags origin "refs/tags/$tag" | grep -q .; then
167+
echo "exists=true" >> "$GITHUB_OUTPUT"
168+
echo "Tag $tag already exists on origin — nothing to release."
159169
else
160-
echo "Tag $GITHUB_REF_NAME (commit $GITHUB_SHA) is NOT on master — release will be skipped"
161-
echo "Merge your PR to master first, then re-tag the merge commit."
162-
echo "on_master=false" >> "$GITHUB_OUTPUT"
170+
echo "exists=false" >> "$GITHUB_OUTPUT"
171+
echo "Tag $tag does not exist yet — will create release."
163172
fi
164173
165-
release:
166-
name: attach prebuilds to GitHub Release
167-
needs: [prebuild, gate-release]
168-
if: needs.gate-release.outputs.on_master == 'true'
169-
runs-on: ubuntu-latest
170-
permissions:
171-
contents: write
172-
steps:
173-
- uses: actions/checkout@v5
174-
175174
- name: Download all prebuild artifacts
175+
if: steps.check_tag.outputs.exists == 'false'
176176
uses: actions/download-artifact@v8
177177
with:
178178
pattern: prebuilds-*
179179
path: artifacts
180180
merge-multiple: true
181181

182182
- name: List downloaded assets
183+
if: steps.check_tag.outputs.exists == 'false'
183184
run: ls -la artifacts/
184185

185-
- name: Create / update Release and upload assets
186+
- name: Create tag and Release, upload assets
187+
if: steps.check_tag.outputs.exists == 'false'
186188
uses: softprops/action-gh-release@v2
187189
with:
188-
# Auto-uses the tag from github.ref. Idempotent: re-runs on the same
189-
# tag will update the existing release and overwrite asset names
190-
# that collide.
190+
# softprops/action-gh-release creates the tag itself when given
191+
# `tag_name` + `target_commitish`. It uses GITHUB_TOKEN, so the
192+
# resulting tag push does NOT re-trigger this workflow (GH's
193+
# rule: events from GITHUB_TOKEN don't cascade).
194+
tag_name: ${{ steps.version.outputs.tag }}
195+
target_commitish: ${{ github.sha }}
196+
name: ${{ steps.version.outputs.tag }}
191197
files: artifacts/*.tar.gz
192198
generate_release_notes: true
193199
fail_on_unmatched_files: true

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-expat",
3-
"version": "2.4.2",
3+
"version": "2.4.3",
44
"main": "./lib/node-expat",
55
"gypfile": true,
66
"description": "NodeJS binding for fast XML parsing.",

0 commit comments

Comments
 (0)