From 2d952a10d271637108dad1dd57d2486b7fd82424 Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Fri, 3 Oct 2025 11:32:43 +0200 Subject: [PATCH] feat: enable trusted publishing Minor workflow changes to use GitHub OIDC for trusted publishing to npm. Allows using two GITHUB_TOKENS, one needed for OIDC + npm release. And the other that has permission to create tags. If we used just one, the token would either not have OIDC permissions (can't read id_token) or wouldn't have permission to create tags. --- .github/workflows/main-ci-release.yml | 88 -------------- .../{release-stable.yml => publish.yml} | 111 ++++++++++++++++-- scripts/release-canary.ts | 11 ++ scripts/release-stable.ts | 13 ++ 4 files changed, 125 insertions(+), 98 deletions(-) delete mode 100644 .github/workflows/main-ci-release.yml rename .github/workflows/{release-stable.yml => publish.yml} (64%) diff --git a/.github/workflows/main-ci-release.yml b/.github/workflows/main-ci-release.yml deleted file mode 100644 index c3d886524..000000000 --- a/.github/workflows/main-ci-release.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Main CI & Canary Release - -on: - push: - branches: [master] -permissions: - actions: read - contents: write - id-token: write - -env: - NODE_VERSION: '20' - -jobs: - ci-core: - name: Core Packages CI - uses: ./.github/workflows/ci-core.yml - permissions: - actions: read - contents: read - - ci-supabase-js: - name: Supabase-JS Integration CI - uses: ./.github/workflows/ci-supabase-js.yml - permissions: - actions: read - contents: read - - ci-auth-js-node18: - name: Auth-JS Node.js 18 Compatibility - uses: ./.github/workflows/ci-auth-js-node18.yml - permissions: - actions: read - contents: read - - # ========================================== - # CANARY RELEASE (only on master, after all CI passes) - # ========================================== - - release-canary: - name: Release Canary - runs-on: ubuntu-latest - needs: [ci-core, ci-supabase-js, ci-auth-js-node18] - # Only run on master branch pushes, and only if all CI jobs succeeded - if: | - github.ref == 'refs/heads/master' && - github.event_name == 'push' && - needs.ci-core.result == 'success' && - needs.ci-supabase-js.result == 'success' && - needs.ci-auth-js-node18.result == 'success' - 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: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - run: npm ci --legacy-peer-deps - - name: Configure git - run: | - git config --global user.name "supabase-releaser[bot]" - git config --global user.email "supabase-releaser[bot]@users.noreply.github.com" - - - name: Release canary version - id: release - run: | - echo "Running nx release..." - npm run release-canary - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_CONFIG_PROVENANCE: true - # Needed to push tags - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/release-stable.yml b/.github/workflows/publish.yml similarity index 64% rename from .github/workflows/release-stable.yml rename to .github/workflows/publish.yml index 463a00139..b7a615e5a 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,11 @@ -name: Release Stable +# Consolidates canary and stable releases into single workflow +# Trusted workflow for publishing to npm + +name: Publish releases on: + push: + branches: [master] workflow_dispatch: inputs: version_specifier: @@ -12,10 +17,11 @@ env: NODE_VERSION: '20' jobs: - release-stable: + release-stable: # stable releases can only be manually triggered + if: ${{ github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest permissions: - contents: write + contents: read id-token: write steps: @@ -58,7 +64,7 @@ jobs: with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.PRIVATE_KEY }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -92,10 +98,8 @@ jobs: - name: Release env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: true - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags run: | npm run release-stable -- --versionSpecifier "${{ github.event.inputs.version_specifier }}" @@ -109,7 +113,10 @@ jobs: docs-after-stable-release: name: Generate Documentation needs: release-stable - if: needs.release-stable.result == 'success' + if: ${{ + github.event_name == 'workflow_dispatch && + needs.release-stable.result == 'success' + }} uses: ./.github/workflows/docs.yml permissions: actions: read @@ -119,7 +126,10 @@ jobs: name: Trigger Update JS Libs runs-on: ubuntu-latest needs: release-stable - if: needs.release-stable.result == 'success' + if: ${{ + github.event_name == 'workflow_dispatch' && + needs.release-stable.result == 'success' + }} steps: - name: Generate token id: app-token @@ -147,9 +157,11 @@ jobs: name: Trigger Supabase Docs Update runs-on: ubuntu-latest needs: [release-stable, docs-after-stable-release] - if: | + 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 @@ -173,3 +185,82 @@ jobs: source: 'supabase-js-stable-release' } }); + + # preview jobs + ci-core: + if: ${{ github.event_name == 'push' }} + name: Core Packages CI + uses: ./.github/workflows/ci-core.yml + permissions: + actions: read + contents: read + + ci-supabase-js: + if: ${{ github.event_name == 'push' }} + name: Supabase-JS Integration CI + uses: ./.github/workflows/ci-supabase-js.yml + permissions: + actions: read + contents: read + + ci-auth-js-node18: + if: ${{ github.event_name == 'push' }} + name: Auth-JS Node.js 18 Compatibility + uses: ./.github/workflows/ci-auth-js-node18.yml + permissions: + actions: read + contents: read + + # ========================================== + # CANARY RELEASE (only on master, after all CI passes) + # ========================================== + + release-canary: + name: Release Canary + runs-on: ubuntu-latest + needs: [ci-core, ci-supabase-js, ci-auth-js-node18] + permissions: + contents: read + id-token: write + # Only run on master branch pushes, and only if all CI jobs succeeded + if: | + github.ref == 'refs/heads/master' && + github.event_name == 'push' && + needs.ci-core.result == 'success' && + needs.ci-supabase-js.result == 'success' && + needs.ci-auth-js-node18.result == 'success' + 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: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm ci --legacy-peer-deps + - name: Configure git + run: | + git config --global user.name "supabase-releaser[bot]" + git config --global user.email "supabase-releaser[bot]@users.noreply.github.com" + + - name: Release canary version + id: release + run: | + echo "Running nx release..." + npm run release-canary + env: + NPM_CONFIG_PROVENANCE: true + RELEASE_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # used for tags diff --git a/scripts/release-canary.ts b/scripts/release-canary.ts index 448a667dc..201d94765 100644 --- a/scripts/release-canary.ts +++ b/scripts/release-canary.ts @@ -18,6 +18,15 @@ import { execSync } from 'child_process' execSync('npx nx run-many --target=build --all', { stdio: 'inherit' }) console.log('✅ Build complete\n') + // 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 + await releaseChangelog({ versionData: projectsVersionData, version: workspaceVersion, @@ -26,6 +35,8 @@ import { execSync } from 'child_process' stageChanges: false, }) + // npm publish with OIDC + process.env.GITHUB_TOKEN = gh_token_bak const publishResult = await releasePublish({ registry: 'https://registry.npmjs.org/', access: 'public', diff --git a/scripts/release-stable.ts b/scripts/release-stable.ts index e77bb29cd..bf3b60b83 100644 --- a/scripts/release-stable.ts +++ b/scripts/release-stable.ts @@ -56,6 +56,15 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) { execSync('npx nx run-many --target=build --all', { stdio: 'inherit' }) console.log('✅ Build complete\n') + // 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 + const result = await releaseChangelog({ versionData: projectsVersionData, version: workspaceVersion, @@ -64,6 +73,8 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) { stageChanges: false, }) + // npm publish with OIDC + process.env.GITHUB_TOKEN = gh_token_bak const publishResult = await releasePublish({ registry: 'https://registry.npmjs.org/', access: 'public', @@ -82,6 +93,8 @@ if (!validSpecifiers.includes(versionSpecifier) && !isValidVersion) { } // ---- Create release branch + PR ---- + // switch back to the releaser GitHub token + process.env.GITHUB_TOKEN = process.env.RELEASE_GITHUB_TOKEN const version = result.workspaceChangelog?.releaseVersion.rawVersion || workspaceVersion // Validate version to prevent command injection