99 publish :
1010 runs-on : ubuntu-latest
1111 permissions :
12- contents : write
12+ contents : write # needed only to push the release tag
1313 steps :
1414 - uses : actions/checkout@v4
1515
@@ -28,30 +28,40 @@ jobs:
2828 - name : Build
2929 run : npm run build
3030
31- # Fully automatic release: if the version in package.json is already on
32- # npm, bump the patch version right here and push the bump back to
33- # master. (Pushes made with GITHUB_TOKEN don't re-trigger workflows, so
34- # this cannot loop; [skip ci] is extra insurance.)
35- - name : Auto-bump version if already published
31+ # Master is a protected branch, so the workflow never commits or pushes
32+ # to it. The release version is derived from the npm registry instead:
33+ # if package.json already holds a NEW version (manual bump in a PR),
34+ # that wins; otherwise the published patch version is incremented. The
35+ # bump happens only inside this runner.
36+ - name : Resolve release version
37+ id : version
3638 run : |
3739 NAME=$(node -p "require('./package.json').name")
3840 LOCAL=$(node -p "require('./package.json').version")
3941 PUBLISHED=$(npm view "$NAME" version 2>/dev/null || echo "none")
40- echo "local=$LOCAL published=$PUBLISHED"
41- if [ "$LOCAL" = "$PUBLISHED" ]; then
42- git config user.name "github-actions[bot]"
43- git config user.email "github-actions[bot]@users.noreply.github.com"
44- # Sync with master first — re-runs and racing pushes otherwise
45- # operate on a stale checkout and get rejected.
46- git pull --rebase origin master
47- RESYNCED=$(node -p "require('./package.json').version")
48- if [ "$RESYNCED" = "$PUBLISHED" ]; then
49- npm version patch -m "release v%s [skip ci]"
50- git push origin master --follow-tags
51- fi
52- fi
42+ NEXT=$(node -e "
43+ const local = '$LOCAL';
44+ const pub = '$PUBLISHED';
45+ const cmp = (a, b) => {
46+ const A = a.split('.').map(Number), B = b.split('.').map(Number);
47+ for (let i = 0; i < 3; i++) { if ((A[i] || 0) !== (B[i] || 0)) return (A[i] || 0) - (B[i] || 0); }
48+ return 0;
49+ };
50+ if (pub === 'none' || cmp(local, pub) > 0) { console.log(local); }
51+ else { const p = pub.split('.').map(Number); p[2] += 1; console.log(p.join('.')); }
52+ ")
53+ echo "local=$LOCAL published=$PUBLISHED releasing=$NEXT"
54+ npm version "$NEXT" --no-git-tag-version --allow-same-version
55+ echo "next=$NEXT" >> "$GITHUB_OUTPUT"
5356
5457 - name : Publish
5558 run : npm publish
5659 env :
5760 NODE_AUTH_TOKEN : ${{ secrets.NPM_TOKEN }}
61+
62+ # Tag pushes are allowed on protected branches, so releases stay
63+ # traceable in git even though no commit lands on master.
64+ - name : Tag release
65+ run : |
66+ git tag "v${{ steps.version.outputs.next }}" || exit 0
67+ git push origin "v${{ steps.version.outputs.next }}" || true
0 commit comments