diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2419cc27..c84a6196 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 @@ -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 @@ -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]" @@ -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 }}" @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/.github/workflows/slack-notify.yml b/.github/workflows/slack-notify.yml index c1876a19..d228ace8 100644 --- a/.github/workflows/slack-notify.yml +++ b/.github/workflows/slack-notify.yml @@ -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 }} diff --git a/scripts/release-stable.ts b/scripts/release-stable.ts index a413a426..51342c34 100644 --- a/scripts/release-stable.ts +++ b/scripts/release-stable.ts @@ -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, @@ -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, @@ -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', @@ -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( @@ -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)