Publish releases #22
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish releases | |
| # Consolidates canary and stable releases into single workflow | |
| # Trusted workflow for publishing to npm | |
| on: | |
| push: | |
| branches: [master] | |
| workflow_dispatch: | |
| inputs: | |
| version_specifier: | |
| description: 'Semver bump (patch|minor|major|pre*) or exact version (v1.2.3)' | |
| required: true | |
| type: string | |
| env: | |
| NODE_VERSION: '20' | |
| jobs: | |
| release-stable: # stable releases can only be manually triggered | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Generate GitHub App token (with org members:read) | |
| 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: ${{ steps.app-token.outputs.token }} | |
| script: | | |
| const org = 'supabase' | |
| const { actor } = context | |
| async function isOrgAdmin() { | |
| try { | |
| const res = await github.rest.orgs.getMembershipForUser({ org, username: actor }) | |
| return res?.status === 200 && res.data?.role === 'admin' && res.data?.state === 'active' | |
| } catch (e) { | |
| console.log('Org membership check failed', e) | |
| return false | |
| } | |
| } | |
| async function resolveTeamSlug(preferredSlugs) { | |
| try { | |
| const teams = await github.paginate(github.rest.teams.list, { org }) | |
| const lower = (s) => (s || '').toLowerCase() | |
| const candidates = preferredSlugs.map(lower) | |
| const team = teams.find((t) => { | |
| const slug = lower(t.slug) | |
| const name = lower(t.name) | |
| return candidates.includes(slug) || candidates.includes(name) | |
| }) | |
| return team?.slug | |
| } catch (e) { | |
| console.log('Failed to list teams', e) | |
| return undefined | |
| } | |
| } | |
| async function isTeamMemberByResolvedSlug(preferredSlugs) { | |
| const resolved = await resolveTeamSlug(preferredSlugs) | |
| if (!resolved) return false | |
| try { | |
| const res = await github.rest.teams.getMembershipForUserInOrg({ | |
| org, | |
| team_slug: resolved, | |
| username: actor, | |
| }) | |
| return res?.status === 200 | |
| } catch (err) { | |
| console.log(`Membership check failed for slug ${resolved}`, err) | |
| return false | |
| } | |
| } | |
| const isAdminOrg = await isOrgAdmin() | |
| const isAdminTeam = await isTeamMemberByResolvedSlug(['admin','admins','owners']) | |
| const isClientLibs = await isTeamMemberByResolvedSlug(['client-libs','clientlibs','client-libraries']) | |
| const isMember = Boolean(isAdminOrg || isAdminTeam || isClientLibs) | |
| core.setOutput('is_team_member', isMember ? 'true' : 'false') | |
| - name: Fail if not authorized | |
| 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 | |
| # - uses: actions/checkout@v5 | |
| # with: | |
| # fetch-depth: 0 |