Skip to content

Commit 81e68f4

Browse files
authored
feat: release workflow RC versioning and publish reliability (#164)
The release workflow had three issues: 1. `npm version` failed with "Version not changed" when release-please had already updated `package.json` to the release version. Fixed by adding `--allow-same-version`. 2. RC version always predicted a minor bump (e.g. 0.9.0-rc.N) regardless of whether the next release is a patch. Fixed by reading `package.json` directly from the release-please PR branch instead of parsing the PR title or guessing from git history. RC numbering now uses `github.run_number` instead of `git describe` (which was unreliable with no remote tags). 3. Re-running a failed stable release workflow would error with "cannot publish over a previously published version". Fixed by checking npm before publishing and skipping if the version already exists.
1 parent 0845d56 commit 81e68f4

3 files changed

Lines changed: 65 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616
runs-on: ubuntu-latest
1717

1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2020

2121
- name: Set up Node
22-
uses: actions/setup-node@v4
22+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
2323

2424
- run: npm ci
2525

@@ -36,10 +36,10 @@ jobs:
3636
runs-on: ubuntu-latest
3737

3838
steps:
39-
- uses: actions/checkout@v4
39+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
4040

4141
- name: Set up Node
42-
uses: actions/setup-node@v4
42+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
4343

4444
- run: npm ci
4545

.github/workflows/conventional-commits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
env:
2727
EVENT: ${{ toJSON(github.event) }}
2828
steps:
29-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
3030
with:
3131
sparse-checkout: |
3232
.github

.github/workflows/release.yml

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ on:
99
permissions:
1010
contents: read
1111

12+
concurrency:
13+
group: release-${{ github.ref }}
14+
cancel-in-progress: false
15+
1216
jobs:
1317
release:
1418
runs-on: ubuntu-latest
@@ -20,21 +24,21 @@ jobs:
2024
steps:
2125
- name: Generate GitHub App token
2226
id: generate-token
23-
uses: actions/create-github-app-token@v1
27+
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf
2428
with:
2529
app-id: ${{ secrets.GH_APP_ID }}
2630
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
2731

28-
- uses: googleapis/release-please-action@v4
32+
- uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38
2933
id: release
3034
with:
3135
token: ${{ steps.generate-token.outputs.token }}
3236

33-
- uses: actions/checkout@v4
37+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
3438
with:
3539
fetch-depth: 0
3640

37-
- uses: actions/setup-node@v4
41+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
3842
with:
3943
node-version: 22
4044
registry-url: "https://registry.npmjs.org"
@@ -43,9 +47,15 @@ jobs:
4347

4448
- name: Determine version
4549
id: version
50+
env:
51+
RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
52+
RELEASE_VERSION: ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}
53+
PR_HEAD_BRANCH: ${{ steps.release.outputs.pr && fromJSON(steps.release.outputs.pr).headBranchName }}
4654
run: |
47-
if [ "${{ steps.release.outputs.release_created }}" == "true" ]; then
48-
VERSION="${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}"
55+
set -euo pipefail
56+
57+
if [ "$RELEASE_CREATED" == "true" ]; then
58+
VERSION="$RELEASE_VERSION"
4959
TAG="latest"
5060
5161
# For release branches, check if this is the latest release branch
@@ -57,20 +67,35 @@ jobs:
5767
fi
5868
fi
5969
else
60-
# RC only for main branch
70+
# RC only on main
6171
if [[ "$GITHUB_REF" != refs/heads/main ]]; then
6272
echo "skip=true" >> $GITHUB_OUTPUT
6373
exit 0
6474
fi
6575
66-
# RC version from git describe, bump minor
67-
DESCRIBE=$(git describe --tags --always --match 'v*' --exclude '*-rc*')
68-
BASE=$(echo "$DESCRIBE" | sed 's/^v//' | cut -d'-' -f1)
69-
MAJOR=$(echo "$BASE" | cut -d'.' -f1)
70-
MINOR=$(echo "$BASE" | cut -d'.' -f2)
71-
COMMITS=$(echo "$DESCRIBE" | cut -d'-' -f2)
72-
NEXT_MINOR=$((MINOR + 1))
73-
VERSION="${MAJOR}.${NEXT_MINOR}.0-rc.${COMMITS}"
76+
if [ -z "$PR_HEAD_BRANCH" ]; then
77+
# No pending release PR — skip to avoid version conflicts
78+
echo "skip=true" >> $GITHUB_OUTPUT
79+
exit 0
80+
fi
81+
82+
# Validate branch name to prevent git ref injection
83+
if [[ ! "$PR_HEAD_BRANCH" =~ ^[A-Za-z0-9._/-]+$ ]] || \
84+
[[ "$PR_HEAD_BRANCH" =~ \.\.|//|@\{|^-|/$ ]]; then
85+
echo "Invalid PR_HEAD_BRANCH: $PR_HEAD_BRANCH"
86+
exit 1
87+
fi
88+
89+
git fetch origin "refs/heads/$PR_HEAD_BRANCH"
90+
NEXT_VERSION=$(git show "FETCH_HEAD:package.json" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")
91+
92+
# Validate strict semver before use
93+
if [[ ! "$NEXT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
94+
echo "Invalid version in PR branch package.json: $NEXT_VERSION"
95+
exit 1
96+
fi
97+
98+
VERSION="${NEXT_VERSION}-rc.${{ github.run_number }}"
7499
TAG="rc"
75100
fi
76101
echo "version=$VERSION" >> $GITHUB_OUTPUT
@@ -79,20 +104,38 @@ jobs:
79104
- name: Build
80105
if: ${{ steps.version.outputs.skip != 'true' }}
81106
run: |
107+
set -euo pipefail
82108
echo "export const VERSION = '${{ steps.version.outputs.version }}';" > src/version.ts
83109
npm ci
84110
npm run build
85-
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
111+
npm version "${{ steps.version.outputs.version }}" --no-git-tag-version --allow-same-version
86112
87113
- name: Publish
88114
if: ${{ steps.version.outputs.skip != 'true' }}
89-
run: npm publish --provenance --tag ${{ steps.version.outputs.tag }}
115+
run: |
116+
set -euo pipefail
117+
NPM_VIEW_STDERR=$(mktemp)
118+
EXISTING=$(npm view "@supabase/ssr@${{ steps.version.outputs.version }}" version 2>"$NPM_VIEW_STDERR") || STATUS=$?
119+
if [ -n "$EXISTING" ]; then
120+
echo "Version ${{ steps.version.outputs.version }} already published on npm, skipping."
121+
rm -f "$NPM_VIEW_STDERR"
122+
exit 0
123+
elif [ "${STATUS:-0}" -ne 0 ] && ! grep -qiE 'E404|not found' "$NPM_VIEW_STDERR"; then
124+
echo "npm view failed unexpectedly:"
125+
cat "$NPM_VIEW_STDERR"
126+
rm -f "$NPM_VIEW_STDERR"
127+
exit 1
128+
fi
129+
rm -f "$NPM_VIEW_STDERR"
130+
131+
npm publish --provenance --tag "${{ steps.version.outputs.tag }}"
90132
91133
- name: Create GitHub pre-release
92134
if: ${{ steps.version.outputs.tag == 'rc' }}
93135
env:
94136
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
95137
run: |
138+
set -euo pipefail
96139
gh release create "v${{ steps.version.outputs.version }}" \
97140
--title "v${{ steps.version.outputs.version }}" \
98141
--generate-notes \

0 commit comments

Comments
 (0)