-
Notifications
You must be signed in to change notification settings - Fork 1
196 lines (170 loc) · 8.25 KB
/
release.yml
File metadata and controls
196 lines (170 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
name: Release
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run only (no git push, no tags, no npm publish)'
required: false
default: true
type: boolean
permissions:
contents: write
id-token: write
issues: write
pull-requests: write
# Allow GitHub Actions to bypass branch protection
# This is required for semantic-release to push version updates
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Verify admin permissions
run: |
# Use the repository's permission endpoint which works for both personal and org repos
RESPONSE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission")
# Extract permission using jq if available, otherwise use grep
if command -v jq &> /dev/null; then
PERMISSION=$(echo "$RESPONSE" | jq -r '.permission // empty')
else
PERMISSION=$(echo "$RESPONSE" | grep -o '"permission":"[^"]*"' | head -1 | cut -d'"' -f4)
fi
if [ -z "$PERMISSION" ]; then
echo "Warning: Could not determine permission level. Response: $RESPONSE"
echo "Note: workflow_dispatch requires write access, proceeding..."
exit 0
fi
if [ "$PERMISSION" != "admin" ]; then
echo "Error: Only repository admins can trigger releases. Current permission: $PERMISSION"
exit 1
fi
echo "✓ Verified admin permission for ${{ github.actor }}"
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.RELEASE_TOKEN }}
- name: Setup git branch
run: |
git fetch --all --tags --force
git fetch origin '+refs/notes/*:refs/notes/*' || true
git checkout -B main
git branch --set-upstream-to=origin/main main
- name: Ensure master branch exists (for semantic-release validation)
run: |
# semantic-release requires at least one release branch that exists; repo uses main, we declare "master"
if ! git ls-remote --heads origin master 2>/dev/null | grep -q .; then
git checkout -b master
git push origin master
git checkout main
fi
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
always-auth: true
- run: npm ci
- run: npm test --if-present
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Add semantic-release note (dry run only)
if: github.event.inputs.dry_run == 'true'
run: |
set -e
if ! git rev-parse --verify v1.0.0-beta.1 >/dev/null 2>&1; then exit 0; fi
git notes --ref semantic-release-v1.0.0-beta.1 add -f -m '{"channels":["beta"]}' v1.0.0-beta.1
echo "Added semantic-release note to v1.0.0-beta.1 (local only for dry run)."
- name: Dry run mode notice
if: github.event.inputs.dry_run == 'true'
run: |
echo "=============================================="
echo " DRY RUN MODE - No commits, tags, or publish"
echo "=============================================="
echo "Semantic-release will show what WOULD happen."
echo "To perform a real release, run again and uncheck 'Dry run only'."
echo ""
- name: Ensure v1.0.0-beta.1 exists locally (dry run only)
if: github.event.inputs.dry_run == 'true'
run: |
if ! git rev-parse --verify "v1.0.0-beta.1" >/dev/null 2>&1; then
# So semantic-release sees a "previous release" and suggests 1.0.0-beta.2 for new commits
PARENT=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)
git tag -a "v1.0.0-beta.1" "$PARENT" -m "chore: initial beta release (dry-run placeholder)"
echo "Created local tag v1.0.0-beta.1 at $PARENT so semantic-release can compute next version (1.0.0-beta.2)."
else
echo "Tag v1.0.0-beta.1 already exists."
fi
- name: Get version before semantic-release
id: version-before
run: |
VERSION_BEFORE=$(node -p "require('./package.json').version")
echo "version=$VERSION_BEFORE" >> $GITHUB_OUTPUT
echo "Current version: $VERSION_BEFORE"
- name: Release with semantic-release
id: release
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -e
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
echo "Running semantic-release in DRY RUN mode..."
npx semantic-release --dry-run 2>&1 | tee semantic-release.log || true
grep -oE "The next release version is [^[:space:]]+" semantic-release.log 2>/dev/null | sed 's/The next release version is //' > next-version.txt || echo "" > next-version.txt
else
npx semantic-release
fi
# npm publish uses OIDC (id-token: write + --provenance). No NPM_TOKEN needed.
# Require on npmjs.com: Package → Package settings → Trusted publishers →
# Add: GitHub Actions, org cloudinary-devs, repo create-cloudinary-react, workflow release.yml
# npm trusted publishing (OIDC) requires npm CLI 11.5.1+; Node 20 ships with npm 9.x.
# Force OIDC-only: override NPM_CONFIG_USERCONFIG so npm ignores setup-node's .npmrc (which may reference a stale token).
- name: Publish to npm using trusted publishing
if: github.event.inputs.dry_run != 'true'
env:
NODE_AUTH_TOKEN: ''
NPM_TOKEN: ''
NPM_CONFIG_USERCONFIG: '${{ runner.temp }}/.npmrc-oidc'
run: |
echo "=== Publishing to npm with trusted publishing (OIDC) ==="
unset NODE_AUTH_TOKEN NPM_TOKEN 2>/dev/null || true
# Config that has only registry — no _authToken — so npm uses OIDC
echo "registry=https://registry.npmjs.org/" > "$NPM_CONFIG_USERCONFIG"
# OIDC for publish requires npm 11.5.1+ (Node 20 ships with npm 9.x)
npm install -g npm@latest
npm --version
# Get versions
VERSION_BEFORE="${{ steps.version-before.outputs.version }}"
VERSION_AFTER=$(node -p "require('./package.json').version")
echo "Version before: $VERSION_BEFORE"
echo "Version after: $VERSION_AFTER"
# Only publish if semantic-release created a new version
if [ "$VERSION_BEFORE" != "$VERSION_AFTER" ]; then
echo "✓ New version detected: $VERSION_AFTER"
echo "Publishing to npm..."
# Publish using npm publish which supports OIDC/trusted publishing
# --tag latest so installers get the most recent version (npm i create-cloudinary-react / npx create-cloudinary-react)
npm publish --provenance --access public --tag latest
echo "✓ Published $VERSION_AFTER to npm"
else
echo "No version change detected (version: $VERSION_AFTER)"
echo "Skipping npm publish - no new release was created"
fi
- name: Dry run - skip npm publish
if: github.event.inputs.dry_run == 'true'
run: |
echo "=============================================="
echo " DRY RUN - Skipping npm publish"
echo "=============================================="
NEXT_VERSION=$(cat next-version.txt 2>/dev/null || echo "")
if [ -n "$NEXT_VERSION" ]; then
echo "Version that WOULD have been published: $NEXT_VERSION"
else
echo "Version that WOULD have been published: (check semantic-release output above; might be no new release)"
echo "Current package.json: $(node -p "require('./package.json').version")"
fi
echo ""
echo "To publish for real, run the workflow again with 'Dry run only' unchecked."