Skip to content

Commit 7d1e40b

Browse files
authored
chore(release): permissions - github token (#1625)
1 parent 35ce587 commit 7d1e40b

File tree

3 files changed

+86
-49
lines changed

3 files changed

+86
-49
lines changed

.github/workflows/publish.yml

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ jobs:
2424
id-token: write
2525

2626
steps:
27+
- name: Generate token
28+
id: app-token
29+
uses: actions/create-github-app-token@v2
30+
with:
31+
app-id: ${{ secrets.APP_ID }}
32+
private-key: ${{ secrets.PRIVATE_KEY }}
2733
- name: Check if actor is member of admin or client-libs team
2834
id: team-check
2935
uses: actions/github-script@v7
3036
with:
31-
github-token: ${{ secrets.GITHUB_TOKEN }}
37+
github-token: ${{ steps.app-token.outputs.token }}
3238
script: |
3339
const org = 'supabase'
3440
const { actor } = context
@@ -45,24 +51,17 @@ jobs:
4551
return false
4652
}
4753
}
48-
4954
const isAdmin = await isTeamMember('admin')
5055
const isClientLibs = await isTeamMember('client-libs')
5156
const isMember = isAdmin || isClientLibs
5257
core.setOutput('is_team_member', isMember ? 'true' : 'false')
5358
5459
- name: Fail if not authorized
55-
if: steps.team-check.outputs.is_team_member != 'true'
60+
if: ${{ steps.team-check.outputs.is_team_member != 'true' }}
5661
run: |
5762
echo "You must be a member of @supabase/admin or @supabase/client-libs."
5863
exit 1
5964
60-
- name: Generate token
61-
id: app-token
62-
uses: actions/create-github-app-token@v2
63-
with:
64-
app-id: ${{ secrets.APP_ID }}
65-
private-key: ${{ secrets.PRIVATE_KEY }}
6665
- uses: actions/checkout@v5
6766
with:
6867
fetch-depth: 0
@@ -76,8 +75,10 @@ jobs:
7675
# Ensure npm 11.5.1 or later is installed for trusted publishing support
7776
- name: Update npm
7877
run: npm install -g npm@latest
78+
7979
- name: Install dependencies
8080
run: npm ci --legacy-peer-deps
81+
8182
- name: Configure git
8283
run: |
8384
git config --global user.name "supabase-releaser[bot]"
@@ -98,11 +99,12 @@ jobs:
9899
exit 1
99100
fi
100101
101-
- name: Release
102+
- name: Release & create PR
102103
env:
103104
NPM_CONFIG_PROVENANCE: true
104105
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105-
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags
106+
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
107+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
106108
run: |
107109
npm run release-stable -- --versionSpecifier "${{ github.event.inputs.version_specifier }}"
108110
@@ -117,10 +119,7 @@ jobs:
117119
docs-after-stable-release:
118120
name: Generate Documentation
119121
needs: release-stable
120-
if: ${{
121-
github.event_name == 'workflow_dispatch' &&
122-
needs.release-stable.result == 'success'
123-
}}
122+
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' }}
124123
uses: ./.github/workflows/docs.yml
125124
permissions:
126125
actions: read
@@ -130,10 +129,7 @@ jobs:
130129
name: Trigger Update JS Libs
131130
runs-on: ubuntu-latest
132131
needs: release-stable
133-
if: ${{
134-
github.event_name == 'workflow_dispatch' &&
135-
needs.release-stable.result == 'success'
136-
}}
132+
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' }}
137133
steps:
138134
- name: Generate token
139135
id: app-token
@@ -161,11 +157,7 @@ jobs:
161157
name: Trigger Supabase Docs Update
162158
runs-on: ubuntu-latest
163159
needs: [release-stable, docs-after-stable-release]
164-
if: ${{
165-
github.event_name == 'workflow_dispatch' &&
166-
needs.release-stable.result == 'success' &&
167-
needs.docs-after-stable-release.result == 'success'
168-
}}
160+
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' && needs.docs-after-stable-release.result == 'success' }}
169161
steps:
170162
- name: Generate token
171163
id: app-token
@@ -271,7 +263,8 @@ jobs:
271263
env:
272264
NPM_CONFIG_PROVENANCE: true
273265
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
274-
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags
266+
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
267+
275268
notify-stable-failure:
276269
name: Notify Slack for Stable failure
277270
needs: release-stable

.github/workflows/slack-notify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,4 @@ jobs:
6161
EOF
6262
)
6363
64-
curl -X POST -H 'Content-type: application/json' --data "$payload" ${{ secrets.SLACK_NOTIFICATIONS_WEBHOOK }}
64+
curl -X POST -H 'Content-type: application/json' --data "$payload" ${{ secrets.SLACK_CLIENT_LIBS_WEBHOOK }}

scripts/release-stable.ts

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,18 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
3939
process.exit(1)
4040
}
4141

42+
function safeExec(cmd: string, opts = {}) {
43+
try {
44+
return execSync(cmd, { stdio: 'inherit', ...opts })
45+
} catch (err) {
46+
console.error(`❌ Command failed: ${cmd}`)
47+
throw err
48+
}
49+
}
50+
4251
;(async () => {
52+
// --- VERSION AND BUILD ---
53+
4354
const { workspaceVersion, projectsVersionData } = await releaseVersion({
4455
verbose: true,
4556
gitCommit: false,
@@ -49,28 +60,37 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
4960

5061
// Update version.ts files with the new versions
5162
console.log('\n📦 Updating version.ts files...')
52-
execSync('npx tsx scripts/update-version-files.ts', { stdio: 'inherit' })
63+
safeExec('npx tsx scripts/update-version-files.ts')
5364

5465
// Rebuild packages with correct versions
5566
console.log('\n🔨 Rebuilding packages with new versions...')
56-
execSync('npx nx run-many --target=build --all', { stdio: 'inherit' })
67+
safeExec('npx nx run-many --target=build --all')
5768
console.log('✅ Build complete\n')
5869

70+
// --- GIT AUTH SETUP FOR TAGGING/CHANGELOG ---
71+
5972
// releaseChangelog should use the GitHub token with permission for tagging
6073
// before switching the token, backup the GITHUB_TOKEN so that it
6174
// can be restored afterwards and used by releasePublish. We can't use the same
6275
// token, because releasePublish wants a token that has the id_token: write permission
6376
// so that we can use OIDC for trusted publishing
77+
6478
const gh_token_bak = process.env.GITHUB_TOKEN
6579
process.env.GITHUB_TOKEN = process.env.RELEASE_GITHUB_TOKEN
66-
// backup original auth header
67-
const originalAuth = execSync('git config --local http.https://github.com/.extraheader')
68-
.toString()
69-
.trim()
80+
81+
// backup original auth header if exists
82+
let originalAuth = ''
83+
try {
84+
originalAuth = execSync('git config --local http.https://github.com/.extraheader')
85+
.toString()
86+
.trim()
87+
} catch {}
88+
7089
// switch the token used
7190
const authHeader = `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${process.env.RELEASE_GITHUB_TOKEN}`).toString('base64')}`
72-
execSync(`git config --local http.https://github.com/.extraheader "${authHeader}"`)
91+
safeExec(`git config --local http.https://github.com/.extraheader "${authHeader}"`)
7392

93+
// ---- CHANGELOG GENERATION ---
7494
const result = await releaseChangelog({
7595
versionData: projectsVersionData,
7696
version: workspaceVersion,
@@ -79,12 +99,18 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
7999
stageChanges: false,
80100
})
81101

102+
// --- RESTORE GIT AUTH FOR PUBLISHING ---
82103
// npm publish with OIDC
83104
// not strictly necessary to restore the header but do it incase we require it later
84-
execSync(`git config --local http.https://github.com/.extraheader "${originalAuth}"`)
85-
// restore the GH token
105+
if (originalAuth) {
106+
safeExec(`git config --local http.https://github.com/.extraheader "${originalAuth}"`)
107+
} else {
108+
safeExec(`git config --local --unset http.https://github.com/.extraheader || true`)
109+
}
86110
process.env.GITHUB_TOKEN = gh_token_bak
87111

112+
// --- NPM PUBLISH ---
113+
88114
const publishResult = await releasePublish({
89115
registry: 'https://registry.npmjs.org/',
90116
access: 'public',
@@ -95,20 +121,36 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
95121
// Publish gotrue-js as legacy mirror of auth-js
96122
console.log('\n📦 Publishing @supabase/gotrue-js (legacy mirror)...')
97123
try {
98-
execSync('npx tsx scripts/publish-gotrue-legacy.ts --tag=latest', { stdio: 'inherit' })
124+
safeExec('npx tsx scripts/publish-gotrue-legacy.ts --tag=latest')
99125
} catch (error) {
100126
console.error('❌ Failed to publish gotrue-js legacy package:', error)
101127
// Don't fail the entire release if gotrue-js fails
102128
console.log('⚠️ Continuing with release despite gotrue-js publish failure')
103129
}
104130

105-
// ---- Create release branch + PR ----
106-
// switch back to the releaser GitHub token
131+
// ---- CREATE RELEASE BRANCH + PR ----
107132
process.env.GITHUB_TOKEN = process.env.RELEASE_GITHUB_TOKEN
133+
134+
// REMOVE ALL credential helpers and .extraheader IMMEDIATELY BEFORE PUSH
135+
try {
136+
safeExec('git config --global --unset credential.helper || true')
137+
} catch {}
138+
try {
139+
safeExec('git config --local --unset credential.helper || true')
140+
} catch {}
141+
try {
142+
safeExec('git config --local --unset http.https://github.com/.extraheader || true')
143+
} catch {}
144+
145+
// Ensure remote is set again before push
146+
if (process.env.RELEASE_GITHUB_TOKEN) {
147+
const remoteUrl = `https://x-access-token:${process.env.RELEASE_GITHUB_TOKEN}@github.com/supabase/supabase-js.git`
148+
safeExec(`git remote set-url origin "${remoteUrl}"`)
149+
}
150+
108151
const version = result.workspaceChangelog?.releaseVersion.rawVersion || workspaceVersion
109152

110153
// Validate version to prevent command injection
111-
// Version should match semver pattern or be a valid npm version specifier
112154
if (
113155
!version ||
114156
!/^(v?\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?|patch|minor|major|prepatch|preminor|premajor|prerelease)$/.test(
@@ -122,29 +164,31 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
122164
const branchName = `release-${version}`
123165

124166
try {
125-
execSync(`git checkout -b ${branchName}`)
126-
execSync('git add CHANGELOG.md || true')
127-
execSync('git add packages/**/CHANGELOG.md || true')
167+
safeExec(`git checkout -b ${branchName}`)
168+
safeExec('git add CHANGELOG.md || true')
169+
safeExec('git add packages/**/CHANGELOG.md || true')
128170

129171
// Commit changes if any
130172
try {
131-
execSync(`git commit -m "chore(release): publish version ${version}"`)
173+
safeExec(`git commit -m "chore(release): publish version ${version}"`)
132174
} catch {
133175
console.log('No changes to commit')
134176
}
135177

136-
execSync(`git push origin ${branchName}`)
178+
// DEBUG: Show credential config and remote before push
179+
safeExec('git config --local --get http.https://github.com/.extraheader || true')
180+
181+
safeExec(`git push origin ${branchName}`)
137182

138183
// Open PR using GitHub CLI
139-
execSync(
140-
`gh pr create --base master --head ${branchName} --title "chore(release): ${version}" --body "Automated release PR for ${version}"`,
141-
{ stdio: 'inherit' }
184+
safeExec(
185+
`gh pr create --base master --head ${branchName} --title "chore(release): version ${version} changelogs" --body "Automated PR to update changelogs for version ${version}."`
142186
)
143187

144188
// Enable auto-merge
145-
execSync(`gh pr merge --auto --squash`, { stdio: 'inherit' })
189+
safeExec(`gh pr merge --auto --squash`)
146190

147-
execSync('git stash')
191+
safeExec('git stash')
148192
console.log('✅ Stashed package.json changes')
149193
} catch (err) {
150194
console.error('❌ Failed to push release branch or open PR', err)

0 commit comments

Comments
 (0)