Release pipelines from main #5209
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: Release | |
| run-name: ${{ github.event_name == 'issue_comment' && format('Snapshot release by {0}', github.actor) || format('Release pipelines from {0}', github.ref_name) }} | |
| on: | |
| push: | |
| branches: | |
| - main | |
| issue_comment: | |
| types: [created] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'issue_comment' && format('issue-{0}-{1}', github.event.issue.number, github.actor) || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| release: | |
| name: Release | |
| if: ${{ github.event_name == 'push' && github.repository == 'clerk/javascript' }} | |
| runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }} | |
| timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} | |
| permissions: | |
| contents: write | |
| id-token: write | |
| packages: write | |
| pull-requests: write | |
| issues: read | |
| statuses: write | |
| checks: write | |
| steps: | |
| - name: Echo github context | |
| run: echo "$GITHUB_CONTEXT" | |
| env: | |
| GITHUB_CONTEXT: ${{ toJson(github) }} | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| show-progress: false | |
| - name: Setup | |
| id: config | |
| uses: ./.github/actions/init | |
| with: | |
| playwright-enabled: true # Must be present to enable caching on branched workflows | |
| turbo-enabled: false # Release uses --force, so turbo cache is not needed | |
| # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} | |
| # turbo-team: ${{ vars.TURBO_TEAM }} | |
| # turbo-token: ${{ secrets.TURBO_TOKEN }} | |
| turbo-team: "" | |
| turbo-token: "" | |
| - name: Upgrade npm for trusted publishing | |
| run: npm install -g npm@latest | |
| - name: Build release | |
| run: pnpm turbo build $TURBO_ARGS --force | |
| - name: Create Release PR | |
| id: changesets | |
| uses: changesets/action@v1 | |
| with: | |
| commit: "ci(repo): Version packages" | |
| title: "ci(repo): Version packages" | |
| publish: pnpm release | |
| # Workaround for https://github.com/changesets/changesets/issues/421 | |
| version: pnpm version-packages | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.CLERK_COOKIE_PAT }} | |
| HUSKY: "0" | |
| NPM_CONFIG_PROVENANCE: true | |
| - name: Trigger workflows on related repos | |
| if: steps.changesets.outputs.published == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| retries: 3 | |
| retry-exempt-status-codes: 400,401 | |
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | |
| script: | | |
| const preMode = require("fs").existsSync("./.changeset/pre.json"); | |
| if (!preMode) { | |
| const clerkjsVersion = require('./packages/clerk-js/package.json').version; | |
| const clerkUiVersion = require('./packages/ui/package.json').version; | |
| const nextjsVersion = require('./packages/nextjs/package.json').version; | |
| const dispatches = [ | |
| github.rest.actions.createWorkflowDispatch({ | |
| owner: 'clerk', | |
| repo: 'sdk-infra-workers', | |
| workflow_id: 'update-pkg-versions.yml', | |
| ref: 'main', | |
| inputs: { clerkjsVersion, clerkUiVersion } | |
| }), | |
| github.rest.actions.createWorkflowDispatch({ | |
| owner: 'clerk', | |
| repo: 'dashboard', | |
| workflow_id: 'prepare-nextjs-sdk-update.yml', | |
| ref: 'main', | |
| inputs: { version: nextjsVersion } | |
| }), | |
| github.rest.actions.createWorkflowDispatch({ | |
| owner: 'clerk', | |
| repo: 'clerk-docs', | |
| workflow_id: 'typedoc.yml', | |
| ref: 'main', | |
| }), | |
| ]; | |
| await Promise.all(dispatches); | |
| } else{ | |
| core.warning("Changeset in pre-mode should not prepare a ClerkJS production release") | |
| } | |
| - name: Generate notification payload | |
| id: notification | |
| if: steps.changesets.outputs.published == 'true' | |
| run: payload=$(node scripts/notify.mjs '${{ steps.changesets.outputs.publishedPackages }}' '${{ github.actor }}') && echo ::set-output name=payload::${payload//$'\n'/'%0A'} | |
| - name: Send commit log to Slack | |
| id: slack | |
| if: steps.changesets.outputs.published == 'true' | |
| uses: slackapi/slack-github-action@v1.24.0 | |
| with: | |
| payload: ${{ steps.notification.outputs.payload }} | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CHANGELOG_WEBHOOK_URL }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| - name: Notify Slack on failure | |
| if: ${{ always() && steps.changesets.outcome == 'failure' }} | |
| uses: slackapi/slack-github-action@v1.24.0 | |
| with: | |
| payload: | | |
| { | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*:red_circle: Stable release failed*\n*Repo:* `${{ github.repository }}`\n*Workflow:* `${{ github.workflow }}`\n*Commit:* `${{ github.sha }}`\n*Triggered by:* `${{ github.actor }}`\n*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>" | |
| } | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SDK_SLACKER_WEBHOOK_URL }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| canary-release: | |
| name: Canary release | |
| if: ${{ github.event_name == 'push' && github.repository == 'clerk/javascript' }} | |
| runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }} | |
| timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} | |
| env: | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| TURBO_TEAM: ${{ vars.TURBO_TEAM }} | |
| TURBO_CACHE: remote:rw | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| fetch-tags: false | |
| filter: 'blob:none' | |
| - name: Setup | |
| id: config | |
| uses: ./.github/actions/init | |
| with: | |
| turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} | |
| turbo-team: ${{ vars.TURBO_TEAM }} | |
| turbo-token: ${{ secrets.TURBO_TOKEN }} | |
| playwright-enabled: true # Must be present to enable caching on branched workflows | |
| - name: Upgrade npm for trusted publishing | |
| run: npm install -g npm@latest | |
| - name: Version packages for canary | |
| id: version-packages | |
| run: pnpm version-packages:canary | tail -1 >> "$GITHUB_OUTPUT" | |
| - name: Build release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm turbo build $TURBO_ARGS | |
| - name: Canary release | |
| id: publish | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm release:canary | |
| env: | |
| NPM_CONFIG_PROVENANCE: true | |
| - name: Trigger workflows on related repos | |
| if: steps.publish.outcome == 'success' | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| retries: 3 | |
| retry-exempt-status-codes: 400,401 | |
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | |
| script: | | |
| const clerkjsVersion = require('./packages/clerk-js/package.json').version; | |
| const clerkUiVersion = require('./packages/ui/package.json').version; | |
| const nextjsVersion = require('./packages/nextjs/package.json').version; | |
| const dispatches = [ | |
| github.rest.actions.createWorkflowDispatch({ | |
| owner: 'clerk', | |
| repo: 'sdk-infra-workers', | |
| workflow_id: 'update-pkg-versions.yml', | |
| ref: 'main', | |
| inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } | |
| }), | |
| ]; | |
| if (nextjsVersion.includes('canary')) { | |
| console.log('clerk/nextjs changed, will notify clerk/accounts'); | |
| dispatches.push( | |
| github.rest.actions.createWorkflowDispatch({ | |
| owner: 'clerk', | |
| repo: 'accounts', | |
| workflow_id: 'release-staging.yml', | |
| ref: 'main', | |
| inputs: { version: nextjsVersion } | |
| }), | |
| ); | |
| } | |
| await Promise.all(dispatches); | |
| - name: Notify Slack on failure | |
| if: ${{ always() && steps.publish.outcome == 'failure' }} | |
| uses: slackapi/slack-github-action@v1.24.0 | |
| with: | |
| payload: | | |
| { | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*:red_circle: Canary release failed*\n*Repo:* `${{ github.repository }}`\n*Workflow:* `${{ github.workflow }}`\n*Commit:* `${{ github.sha }}`\n*Triggered by:* `${{ github.actor }}`\n*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>" | |
| } | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SDK_SLACKER_WEBHOOK_URL }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| snapshot-release: | |
| name: Snapshot release | |
| if: ${{ github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '!snapshot') && github.repository == 'clerk/javascript' && github.event.issue.pull_request }} | |
| runs-on: ${{ vars.RUNNER_LARGE || 'ubuntu-latest-l' }} | |
| timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| - name: Limit action to Clerk members | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| retries: 3 | |
| retry-exempt-status-codes: 400,401 | |
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | |
| script: | | |
| try { | |
| const { data } = await github.rest.orgs.getMembershipForUser({ | |
| org: 'clerk', | |
| username: context.actor | |
| }); | |
| if (data.state !== 'active') { | |
| core.setFailed(`@${context.actor} is not an active member of the Clerk organization`); | |
| } | |
| } catch (e) { | |
| core.setFailed(`@${context.actor} is not a member of the Clerk organization`); | |
| } | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: refs/pull/${{ github.event.issue.number }}/head | |
| fetch-depth: 1 | |
| fetch-tags: false | |
| filter: 'blob:none' | |
| - name: Ensure the PR hasn't changed since initiating the !snapshot command. | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| retries: 3 | |
| retry-exempt-status-codes: 400,401 | |
| script: | | |
| const commentCreated = new Date(context.payload.comment.created_at); | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: 'clerk', | |
| repo: 'javascript', | |
| pull_number: context.issue.number, | |
| }); | |
| const prLastUpdated = new Date(pr.updated_at); | |
| if (prLastUpdated > commentCreated) { | |
| core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command."); | |
| } | |
| - name: Setup | |
| id: config | |
| uses: ./.github/actions/init | |
| with: | |
| turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} | |
| turbo-team: ${{ vars.TURBO_TEAM }} | |
| turbo-token: ${{ secrets.TURBO_TOKEN }} | |
| - name: Upgrade npm for trusted publishing | |
| run: npm install -g npm@latest | |
| - name: Extract snapshot name | |
| id: extract-snapshot-name | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const match = context.payload.comment.body.match(/!snapshot (.*)/) | |
| const name = match && match[1] || ''; | |
| const isKebabCase = name.match(/^[a-z]+(-[a-z]+)*$/) | |
| if(name && !isKebabCase) { | |
| core.setFailed(`Invalid snapshot name: ${name}`); | |
| } | |
| core.setOutput('name', name); | |
| - name: Version packages for snapshot | |
| id: version-packages | |
| run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" | |
| - name: Build release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm turbo build $TURBO_ARGS | |
| - name: Snapshot release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm release:snapshot | |
| env: | |
| NPM_CONFIG_PROVENANCE: true | |
| - name: Package info | |
| if: steps.version-packages.outputs.success == '1' | |
| id: package-info | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require("fs"); | |
| const files = await (await glob.create("./packages/*/package.json")).glob(); | |
| const descriptors = files | |
| .map((file) => { | |
| const { name, version } = JSON.parse(fs.readFileSync(file, "utf8")); | |
| return { name, version }; | |
| }) | |
| .filter(({ version }) => version.includes("-${{ steps.extract-snapshot-name.outputs.name }}")); | |
| let table = `| Package | Version |\n| --- | --- |\n`; | |
| descriptors.forEach(({ name, version }) => { | |
| table += `| ${name} | ${version} |\n`; | |
| }); | |
| const snippets = descriptors | |
| .map( | |
| ({ name, version }) => | |
| `\`${name}\`\n\`\`\`sh\nnpm i ${name}@${version} --save-exact\n\`\`\`` | |
| ) | |
| .join("\n"); | |
| core.setOutput("table", table); | |
| core.setOutput("snippets", snippets); | |
| - name: Update Comment | |
| if: steps.version-packages.outputs.success == '1' | |
| uses: peter-evans/create-or-update-comment@v3.0.0 | |
| with: | |
| comment-id: ${{ github.event.comment.id }} | |
| reactions: heart | |
| - name: Minimize previous snapshot comments | |
| if: steps.version-packages.outputs.success == '1' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100, | |
| }); | |
| const snapshotComments = comments.filter( | |
| (comment) => | |
| comment.body?.includes('the snapshot version command generated the following package versions') | |
| ); | |
| for (const comment of snapshotComments) { | |
| await github.graphql(` | |
| mutation MinimizeComment($id: ID!) { | |
| minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { | |
| minimizedComment { | |
| isMinimized | |
| } | |
| } | |
| } | |
| `, { id: comment.node_id }); | |
| } | |
| - name: Create snapshot release comment | |
| if: steps.version-packages.outputs.success == '1' | |
| uses: peter-evans/create-or-update-comment@v3.0.0 | |
| with: | |
| issue-number: ${{ github.event.issue.number }} | |
| body: | | |
| Hey @${{ github.event.comment.user.login }} - the snapshot version command generated the following package versions: | |
| ${{ steps.package-info.outputs.table }} | |
| Tip: Use the snippet copy button below to quickly install the required packages. | |
| ${{ steps.package-info.outputs.snippets }} | |
| # We're running the CI workflow (where node v20 modules are cached) in | |
| # merge_group and not on main, we need to explicitly cache node_modules here so | |
| # that follow-on branches can use the cached version of node_modules rather | |
| # than recreating them every time. | |
| cache-for-alternate-node-versions: | |
| name: Cache for Alternate Node Versions | |
| if: ${{ github.event_name == 'push' }} | |
| runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }} | |
| timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} | |
| continue-on-error: true | |
| strategy: | |
| matrix: | |
| version: [22] # NOTE: 18 is cached in the main release workflow | |
| steps: | |
| - name: Checkout Repo | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| fetch-tags: false | |
| filter: "blob:none" | |
| show-progress: false | |
| - name: Cache node_modules (Node v${{ matrix.version }}) | |
| uses: ./.github/actions/init | |
| with: | |
| node-version: ${{ matrix.version }} | |
| turbo-enabled: false | |
| turbo-team: "" | |
| turbo-token: "" |