Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b39b04a
fix(release): github token
mandarini Oct 6, 2025
f740bb9
chore(release): temporarily allow all org members to release
mandarini Oct 6, 2025
2a2b38a
chore(ci): restore check
mandarini Oct 6, 2025
54bd858
chore(ci): only trigger cross-repo
mandarini Oct 6, 2025
6ce990c
chore(ci): github logs
mandarini Oct 6, 2025
c0c9b1e
chore(ci): test permissions to push
mandarini Oct 6, 2025
250a984
chore(ci): set origin
mandarini Oct 6, 2025
232e4ac
chore(ci): access token everywhere
mandarini Oct 6, 2025
a668ea9
chore(ci): one more try
mandarini Oct 6, 2025
800d8bb
chore(ci): test toekn push
mandarini Oct 6, 2025
43b734b
chore(ci): unset cred helper
mandarini Oct 6, 2025
d792f52
chore(ci): gh cli
mandarini Oct 6, 2025
d79ae89
chore(ci): only test permissions
mandarini Oct 6, 2025
208fde3
chore(ci): strip unnecessary parts until it works
mandarini Oct 6, 2025
f38c0db
chore(ci): auth token
mandarini Oct 6, 2025
4e87d78
chore(ci): fix script
mandarini Oct 6, 2025
4e23b15
chore(ci): minimal CI job
mandarini Oct 6, 2025
9f80c36
chore(ci): minimal CI job 2
mandarini Oct 6, 2025
7d9b43c
chore(ci): minimal CI job 3
mandarini Oct 6, 2025
7a49f19
chore(ci): restore original settings almost
mandarini Oct 6, 2025
03a8191
chore(ci): restore
mandarini Oct 6, 2025
b3f0a21
chore(ci): test the release with canary
mandarini Oct 6, 2025
73c876a
chore(ci): prems
mandarini Oct 6, 2025
8aeee27
chore(ci): generate gh token on top
mandarini Oct 6, 2025
4c1d51f
chore(ci): more permissions
mandarini Oct 6, 2025
7ca7b85
chore(ci): update webhook var
mandarini Oct 6, 2025
5ab3347
chore(ci): final fix
mandarini Oct 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 18 additions & 25 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ jobs:
id-token: write

steps:
- name: Generate token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Check if actor is member of admin or client-libs team
id: team-check
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const org = 'supabase'
const { actor } = context
Expand All @@ -45,24 +51,17 @@ jobs:
return false
}
}

const isAdmin = await isTeamMember('admin')
const isClientLibs = await isTeamMember('client-libs')
const isMember = isAdmin || isClientLibs
core.setOutput('is_team_member', isMember ? 'true' : 'false')

- name: Fail if not authorized
if: steps.team-check.outputs.is_team_member != 'true'
if: ${{ steps.team-check.outputs.is_team_member != 'true' }}
run: |
echo "You must be a member of @supabase/admin or @supabase/client-libs."
exit 1

- name: Generate token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- uses: actions/checkout@v5
with:
fetch-depth: 0
Expand All @@ -76,8 +75,10 @@ jobs:
# Ensure npm 11.5.1 or later is installed for trusted publishing support
- name: Update npm
run: npm install -g npm@latest

- name: Install dependencies
run: npm ci --legacy-peer-deps

- name: Configure git
run: |
git config --global user.name "supabase-releaser[bot]"
Expand All @@ -98,11 +99,12 @@ jobs:
exit 1
fi

- name: Release
- name: Release & create PR
env:
NPM_CONFIG_PROVENANCE: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
npm run release-stable -- --versionSpecifier "${{ github.event.inputs.version_specifier }}"

Expand All @@ -117,10 +119,7 @@ jobs:
docs-after-stable-release:
name: Generate Documentation
needs: release-stable
if: ${{
github.event_name == 'workflow_dispatch' &&
needs.release-stable.result == 'success'
}}
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' }}
uses: ./.github/workflows/docs.yml
permissions:
actions: read
Expand All @@ -130,10 +129,7 @@ jobs:
name: Trigger Update JS Libs
runs-on: ubuntu-latest
needs: release-stable
if: ${{
github.event_name == 'workflow_dispatch' &&
needs.release-stable.result == 'success'
}}
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' }}
steps:
- name: Generate token
id: app-token
Expand Down Expand Up @@ -161,11 +157,7 @@ jobs:
name: Trigger Supabase Docs Update
runs-on: ubuntu-latest
needs: [release-stable, docs-after-stable-release]
if: ${{
github.event_name == 'workflow_dispatch' &&
needs.release-stable.result == 'success' &&
needs.docs-after-stable-release.result == 'success'
}}
if: ${{ github.event_name == 'workflow_dispatch' && needs.release-stable.result == 'success' && needs.docs-after-stable-release.result == 'success' }}
steps:
- name: Generate token
id: app-token
Expand Down Expand Up @@ -271,7 +263,8 @@ jobs:
env:
NPM_CONFIG_PROVENANCE: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags
RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

notify-stable-failure:
name: Notify Slack for Stable failure
needs: release-stable
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/slack-notify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ jobs:
EOF
)

curl -X POST -H 'Content-type: application/json' --data "$payload" ${{ secrets.SLACK_NOTIFICATIONS_WEBHOOK }}
curl -X POST -H 'Content-type: application/json' --data "$payload" ${{ secrets.SLACK_CLIENT_LIBS_WEBHOOK }}
90 changes: 67 additions & 23 deletions scripts/release-stable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,18 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
process.exit(1)
}

function safeExec(cmd: string, opts = {}) {
try {
return execSync(cmd, { stdio: 'inherit', ...opts })
} catch (err) {
console.error(`❌ Command failed: ${cmd}`)
throw err
}
}

;(async () => {
// --- VERSION AND BUILD ---

const { workspaceVersion, projectsVersionData } = await releaseVersion({
verbose: true,
gitCommit: false,
Expand All @@ -49,28 +60,37 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {

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

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

// --- GIT AUTH SETUP FOR TAGGING/CHANGELOG ---

// releaseChangelog should use the GitHub token with permission for tagging
// before switching the token, backup the GITHUB_TOKEN so that it
// can be restored afterwards and used by releasePublish. We can't use the same
// token, because releasePublish wants a token that has the id_token: write permission
// so that we can use OIDC for trusted publishing

const gh_token_bak = process.env.GITHUB_TOKEN
process.env.GITHUB_TOKEN = process.env.RELEASE_GITHUB_TOKEN
// backup original auth header
const originalAuth = execSync('git config --local http.https://github.com/.extraheader')
.toString()
.trim()

// backup original auth header if exists
let originalAuth = ''
try {
originalAuth = execSync('git config --local http.https://github.com/.extraheader')
.toString()
.trim()
} catch {}

// switch the token used
const authHeader = `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${process.env.RELEASE_GITHUB_TOKEN}`).toString('base64')}`
execSync(`git config --local http.https://github.com/.extraheader "${authHeader}"`)
safeExec(`git config --local http.https://github.com/.extraheader "${authHeader}"`)

// ---- CHANGELOG GENERATION ---
const result = await releaseChangelog({
versionData: projectsVersionData,
version: workspaceVersion,
Expand All @@ -79,12 +99,18 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) {
stageChanges: false,
})

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

// --- NPM PUBLISH ---

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

// ---- Create release branch + PR ----
// switch back to the releaser GitHub token
// ---- CREATE RELEASE BRANCH + PR ----
process.env.GITHUB_TOKEN = process.env.RELEASE_GITHUB_TOKEN

// REMOVE ALL credential helpers and .extraheader IMMEDIATELY BEFORE PUSH
try {
safeExec('git config --global --unset credential.helper || true')
} catch {}
try {
safeExec('git config --local --unset credential.helper || true')
} catch {}
try {
safeExec('git config --local --unset http.https://github.com/.extraheader || true')
} catch {}

// Ensure remote is set again before push
if (process.env.RELEASE_GITHUB_TOKEN) {
const remoteUrl = `https://x-access-token:${process.env.RELEASE_GITHUB_TOKEN}@github.com/supabase/supabase-js.git`
safeExec(`git remote set-url origin "${remoteUrl}"`)
}

const version = result.workspaceChangelog?.releaseVersion.rawVersion || workspaceVersion

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

try {
execSync(`git checkout -b ${branchName}`)
execSync('git add CHANGELOG.md || true')
execSync('git add packages/**/CHANGELOG.md || true')
safeExec(`git checkout -b ${branchName}`)
safeExec('git add CHANGELOG.md || true')
safeExec('git add packages/**/CHANGELOG.md || true')

// Commit changes if any
try {
execSync(`git commit -m "chore(release): publish version ${version}"`)
safeExec(`git commit -m "chore(release): publish version ${version}"`)
} catch {
console.log('No changes to commit')
}

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

safeExec(`git push origin ${branchName}`)

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

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

execSync('git stash')
safeExec('git stash')
console.log('✅ Stashed package.json changes')
} catch (err) {
console.error('❌ Failed to push release branch or open PR', err)
Expand Down