Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 42 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ jobs:
outputs:
version: ${{ steps.version.outputs.version }}
sha: ${{ steps.pr.outputs.sha }}
is_fork: ${{ steps.pr.outputs.is_fork }}
reason: ${{ steps.pr.outputs.reason }}
steps:
- name: React to comment
env:
Expand All @@ -391,27 +391,43 @@ jobs:
gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=eyes --silent

- name: Get PR head SHA
# Runs before checkout: this job executes the PR's package.json scripts
# in the default branch's privileged context, so fork code must never be
# fetched. The fork check plus the MEMBER/OWNER gate above are the trust
# boundary. The freshness check is only a race guard for the honest case
# (a colleague pushes between the comment and this step); it keys on the
# commit date, which is client-settable, and that is acceptable because
# pushing to a same-repo branch already requires write access.
- name: Validate PR source and freshness
id: pr
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
COMMENT_CREATED_AT: ${{ github.event.comment.created_at }}
LC_ALL: C
run: |
pr_json=$(gh api "repos/${GH_REPO}/pulls/${{ github.event.issue.number }}")
sha=$(echo "$pr_json" | jq -r '.head.sha')
head_repo=$(echo "$pr_json" | jq -r '.head.repo.full_name')
base_repo=$(echo "$pr_json" | jq -r '.base.repo.full_name')
is_fork="false"
reject() {
echo "reason=$1" >> "$GITHUB_OUTPUT"
echo "::error::$1"
exit 1
}
if [ "$head_repo" != "$base_repo" ]; then
is_fork="true"
echo "::warning::Snapshot requested on fork PR (head: ${head_repo}) — publish will be skipped."
reject "Snapshots are restricted to branches within ${base_repo} (PR head: ${head_repo})."
fi
head_committed_at=$(gh api "repos/${GH_REPO}/commits/${sha}" --jq '.commit.committer.date')
if [[ "$head_committed_at" > "$COMMENT_CREATED_AT" ]]; then
reject "The PR head moved after \`!snapshot\` was posted (${sha:0:7} committed at ${head_committed_at}). Review the changes and comment \`!snapshot\` again."
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
echo "is_fork=${is_fork}" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v7
with:
ref: ${{ steps.pr.outputs.sha }}
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile

Expand All @@ -429,15 +445,13 @@ jobs:

snapshot-ci:
needs: snapshot
if: needs.snapshot.outputs.is_fork != 'true'
uses: ./.github/workflows/ci.yml
with:
ref: ${{ needs.snapshot.outputs.sha }}
secrets: inherit

snapshot-build:
needs: [snapshot, snapshot-ci]
if: needs.snapshot.outputs.is_fork != 'true'
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.snapshot.outputs.version }}
Expand All @@ -447,7 +461,6 @@ jobs:

snapshot-sign-macos:
needs: [snapshot, snapshot-build]
if: needs.snapshot.outputs.is_fork != 'true'
uses: ./.github/workflows/sign-macos.yml
with:
artifact-prefix: clerk-snapshot
Expand All @@ -460,7 +473,6 @@ jobs:

snapshot-smoke-test:
needs: [snapshot, snapshot-build, snapshot-sign-macos]
if: needs.snapshot.outputs.is_fork != 'true'
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.snapshot.outputs.version }}
Expand All @@ -469,7 +481,6 @@ jobs:

snapshot-publish:
Comment thread
wyattjoh marked this conversation as resolved.
needs: [snapshot, snapshot-build, snapshot-sign-macos, snapshot-smoke-test]
if: needs.snapshot.outputs.is_fork != 'true'
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
Expand All @@ -478,9 +489,23 @@ jobs:
pull-requests: write
id-token: write
steps:
# Independent of the snapshot job's check: this job mints the npm OIDC
# token, so it must not rely on an upstream step for the fork boundary.
- name: Verify PR head is not a fork
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
pr_json=$(gh api "repos/${GH_REPO}/pulls/${{ github.event.issue.number }}")
if [ "$(echo "$pr_json" | jq -r '.head.repo.full_name')" != "$(echo "$pr_json" | jq -r '.base.repo.full_name')" ]; then
echo "::error::Refusing to publish from a fork PR."
exit 1
fi

- uses: actions/checkout@v7
with:
ref: ${{ needs.snapshot.outputs.sha }}
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v7
with:
Expand Down Expand Up @@ -565,10 +590,15 @@ jobs:
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
REASON: ${{ needs.snapshot.outputs.reason }}
run: |
{
echo '## Snapshot failed'
echo ''
echo "The snapshot publish workflow failed. [View the workflow run](${RUN_URL}) for details."
if [ -n "$REASON" ]; then
echo "$REASON"
echo ''
fi
echo "[View the workflow run](${RUN_URL}) for details."
} > /tmp/comment-body.md
gh pr comment "${PR_NUMBER}" --repo "${GH_REPO}" --body-file /tmp/comment-body.md
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Install via npm: `npm install -g clerk@canary`

### Snapshot (`@snapshot`)

Published on-demand from PR branches by commenting `!snapshot` (or `!snapshot <name>`) on a pull request. The commenter must be a member or owner of the repository's organization. `scripts/snapshot.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-<name>.v<YYYYMMDDHHmmss>` (e.g., `0.0.1-snapshot.v20260313145959` or `0.0.1-my-feature.v20260313145959`). The datetime format ensures multiple snapshots from the same PR sort monotonically in semver.
Published on-demand from PR branches by commenting `!snapshot` (or `!snapshot <name>`) on a pull request. The commenter must be a member or owner of the repository's organization, the PR branch must live in `clerk/cli` (fork PRs are rejected before any code is checked out), and the PR's head commit must predate the `!snapshot` comment — if the branch moves after you comment, the run fails and you comment again once you've reviewed the new code. `scripts/snapshot.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-<name>.v<YYYYMMDDHHmmss>` (e.g., `0.0.1-snapshot.v20260313145959` or `0.0.1-my-feature.v20260313145959`). The datetime format ensures multiple snapshots from the same PR sort monotonically in semver.

Install: `npm install -g clerk@<version>` (version is posted as a PR comment after publishing)

Expand Down