Skip to content

fix(cli): list --help commands and options alphabetically #962

fix(cli): list --help commands and options alphabetically

fix(cli): list --help commands and options alphabetically #962

Workflow file for this run

name: Release
on:
push:
branches: [main]
issue_comment:
types: [created]
concurrency:
group: release-${{ github.event_name == 'issue_comment' && format('snapshot-{0}', github.event.issue.number) || 'main' }}
cancel-in-progress: false
permissions:
contents: read
jobs:
# ═══════════════════════════════════════════════════════════════════════
# Push to main → stable or canary release
# ═══════════════════════════════════════════════════════════════════════
ci:
if: github.event_name == 'push'
uses: ./.github/workflows/ci.yml
secrets: inherit
versioning:
needs: [ci]
if: github.event_name == 'push'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.check.outputs.release_created }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: actions/cache/restore@v6
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-
- run: bun install --frozen-lockfile
- name: Check if release needed
id: check
run: bun run scripts/check-release.ts
- id: changesets
uses: changesets/action@v1
with:
version: bun run version-packages
commit: "ci(repo): version packages"
title: "ci(repo): Version Packages"
env:
GITHUB_TOKEN: ${{ github.token }}
# ─── Stable release ────────────────────────────────────────────────
build:
needs: versioning
if: needs.versioning.outputs.release_created == 'true'
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.versioning.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk
secrets: inherit
sign-macos:
needs: [versioning, build]
if: needs.versioning.outputs.release_created == 'true'
uses: ./.github/workflows/sign-macos.yml
with:
artifact-prefix: clerk
secrets:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
smoke-test:
needs: [versioning, build, sign-macos]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.versioning.outputs.version }}
artifact-prefix: clerk
preset: stable
publish-npm:
needs: [versioning, build, sign-macos, smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: "https://registry.npmjs.org"
- run: bun install --frozen-lockfile
- uses: actions/download-artifact@v8
with:
pattern: clerk-*
path: dist/artifacts
- name: Publish packages
run: bun run release
env:
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}
publish-github:
needs: [versioning, build, sign-macos, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: clerk-*
path: dist/artifacts
- name: Upload binaries to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.versioning.outputs.version }}"
for dir in dist/artifacts/clerk-*/; do
target=${dir#dist/artifacts/clerk-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
cp "${dir}clerk${ext}" "${dir}clerk-${target}${ext}"
gh release upload "$tag" "${dir}clerk-${target}${ext}" --clobber
done
homebrew:
needs: [versioning, build, sign-macos, smoke-test, publish-npm]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- uses: actions/download-artifact@v8
with:
pattern: clerk-{darwin-arm64,darwin-x64,linux-arm64,linux-x64}
path: dist/artifacts
- name: Publish Homebrew formula
run: bun run scripts/homebrew.ts --version "$VERSION"
env:
VERSION: ${{ needs.versioning.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
notify-stable-failure:
# `ci` and `versioning` are deliberately excluded: they run on every push to
# main, before the release channel is decided, so a failure there is not a
# stable release failure. `notify-ci-failure` covers those.
if: >-
always() &&
needs.versioning.outputs.release_created == 'true' &&
contains(needs.*.result, 'failure')
needs: [versioning, build, sign-macos, smoke-test, publish-npm, publish-github, homebrew]
uses: ./.github/workflows/notify-failure.yml
with:
workflow-name: Stable release
secrets:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
notify-stable-success:
needs: [versioning, publish-npm, publish-github, homebrew]
if: needs.versioning.outputs.release_created == 'true'
uses: ./.github/workflows/notify-release.yml
with:
version: ${{ needs.versioning.outputs.version }}
secrets:
SLACK_RELEASE_WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }}
# ─── Canary release ────────────────────────────────────────────────
canary-version:
needs: versioning
if: needs.versioning.outputs.release_created != 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: actions/cache/restore@v6
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-
- run: bun install --frozen-lockfile
- name: Version packages for canary
id: version
run: |
bun run version-packages:canary
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"
canary-build:
needs: canary-version
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.canary-version.outputs.version }}
ref: ${{ github.sha }}
artifact-prefix: clerk-canary
secrets: inherit
canary-sign-macos:
needs: [canary-version, canary-build]
uses: ./.github/workflows/sign-macos.yml
with:
artifact-prefix: clerk-canary
secrets:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
canary-smoke-test:
needs: [canary-version, canary-build, canary-sign-macos]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.canary-version.outputs.version }}
artifact-prefix: clerk-canary
preset: canary
canary-publish-github:
needs: [canary-version, canary-build, canary-sign-macos, canary-smoke-test]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: clerk-canary-*
path: dist/artifacts
- name: Create pre-release and upload binaries
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
tag="v${{ needs.canary-version.outputs.version }}"
# Create GitHub Release as a pre-release
gh release view "$tag" --repo "$GH_REPO" 2>/dev/null || \
gh release create "$tag" \
--repo "$GH_REPO" \
--title "$tag" \
--target "${{ github.sha }}" \
--prerelease \
--notes "Canary release \`$tag\`"
# Upload binaries
for dir in dist/artifacts/clerk-canary-*/; do
target=${dir#dist/artifacts/clerk-canary-} && target=${target%/}
ext=""; [[ "$target" == win32-* ]] && ext=".exe"
cp "${dir}clerk${ext}" "${dir}clerk-${target}${ext}"
gh release upload "$tag" "${dir}clerk-${target}${ext}" \
--repo "$GH_REPO" --clobber
done
canary-publish-npm:
needs:
[canary-version, canary-build, canary-smoke-test, canary-publish-github]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing.
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: "https://registry.npmjs.org"
- run: bun install --frozen-lockfile
- uses: actions/download-artifact@v8
with:
pattern: clerk-canary-*
path: dist/artifacts
- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-canary-*/; do
target=${dir#clerk-canary-} && target=${target%/}
mv "$dir" "clerk-${target}"
done
- name: Publish canary packages
run: bun run release:canary --version "$CANARY_VERSION"
env:
CANARY_VERSION: ${{ needs.canary-version.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
GH_TOKEN: ${{ github.token }}
notify-canary-failure:
# Gate on `== 'false'`, never `!= 'true'`: when `versioning` is skipped the
# output is the empty string, which satisfies `!= 'true'` and would fire this
# notification for failures that never reached the canary path.
if: >-
always() &&
needs.versioning.outputs.release_created == 'false' &&
contains(needs.*.result, 'failure')
needs:
[
versioning,
canary-version,
canary-build,
canary-sign-macos,
canary-smoke-test,
canary-publish-github,
canary-publish-npm,
]
uses: ./.github/workflows/notify-failure.yml
with:
workflow-name: Canary release
secrets:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# ─── Pre-release CI ────────────────────────────────────────────────
notify-ci-failure:
# Covers failures in the jobs shared by both channels, which run before the
# release channel is decided and so belong to neither.
if: >-
always() &&
contains(needs.*.result, 'failure')
needs: [ci, versioning]
uses: ./.github/workflows/notify-failure.yml
with:
workflow-name: Main CI
secrets:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# ═══════════════════════════════════════════════════════════════════════
# PR comment (!snapshot) → snapshot release
# ═══════════════════════════════════════════════════════════════════════
snapshot:
# Intentionally restricted to MEMBER and OWNER — external collaborators
# (COLLABORATOR association) cannot trigger snapshots. This is a deliberate
# security boundary: snapshot publishing has npm write access via OIDC.
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '!snapshot') &&
contains(fromJSON('["MEMBER","OWNER"]'), github.event.comment.author_association)
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
outputs:
version: ${{ steps.version.outputs.version }}
sha: ${{ steps.pr.outputs.sha }}
reason: ${{ steps.pr.outputs.reason }}
steps:
- name: React to comment
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=eyes --silent
# 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')
reject() {
echo "reason=$1" >> "$GITHUB_OUTPUT"
echo "::error::$1"
exit 1
}
if [ "$head_repo" != "$base_repo" ]; then
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."
fi
echo "sha=${sha}" >> "$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
- name: Compute snapshot version
id: version
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Extract optional name from "!snapshot <name>" — default to "snapshot"
name=$(echo "$COMMENT_BODY" | sed 's/^!snapshot[[:space:]]*//' | tr -cd 'a-zA-Z0-9-')
if [ -z "$name" ]; then name="snapshot"; fi
bun run version-packages:snapshot -- "$name"
version=$(jq -r '.version' packages/cli/package.json)
echo "version=${version}" >> "$GITHUB_OUTPUT"
snapshot-ci:
needs: snapshot
uses: ./.github/workflows/ci.yml
with:
ref: ${{ needs.snapshot.outputs.sha }}
secrets: inherit
snapshot-build:
needs: [snapshot, snapshot-ci]
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.snapshot.outputs.version }}
ref: ${{ needs.snapshot.outputs.sha }}
artifact-prefix: clerk-snapshot
secrets: inherit
snapshot-sign-macos:
needs: [snapshot, snapshot-build]
uses: ./.github/workflows/sign-macos.yml
with:
artifact-prefix: clerk-snapshot
secrets:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
snapshot-smoke-test:
needs: [snapshot, snapshot-build, snapshot-sign-macos]
uses: ./.github/workflows/smoke-test.yml
with:
version: ${{ needs.snapshot.outputs.version }}
artifact-prefix: clerk-snapshot
preset: snapshot
snapshot-publish:
needs: [snapshot, snapshot-build, snapshot-sign-macos, snapshot-smoke-test]
# Must run on GitHub-hosted runner for npm OIDC trusted publishing
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
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:
node-version: 24
registry-url: "https://registry.npmjs.org"
- run: bun install --frozen-lockfile
- uses: actions/download-artifact@v8
with:
pattern: clerk-snapshot-*
path: dist/artifacts
- name: Rename artifact directories
run: |
cd dist/artifacts
for dir in clerk-snapshot-*/; do
target=${dir#clerk-snapshot-} && target=${target%/}
mv "$dir" "clerk-${target}"
done
- name: Publish snapshot packages
run: bun run release:snapshot --version "$SNAPSHOT_VERSION"
env:
SNAPSHOT_VERSION: ${{ needs.snapshot.outputs.version }}
ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts
- name: React with rocket
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=rocket --silent
- name: Post installation comment
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
SNAPSHOT_VERSION: ${{ needs.snapshot.outputs.version }}
SNAPSHOT_SHA: ${{ needs.snapshot.outputs.sha }}
PR_NUMBER: ${{ github.event.issue.number }}
run: |
short_sha=$(echo "${SNAPSHOT_SHA}" | cut -c1-7)
{
echo '## Snapshot published'
echo ''
echo '```sh'
echo "npm install -g clerk@${SNAPSHOT_VERSION}"
echo '```'
echo ''
echo '| Package | Version |'
echo '|---------|---------|'
echo "| \`clerk\` | \`${SNAPSHOT_VERSION}\` |"
echo ''
echo "> Published from ${short_sha}"
} > /tmp/comment-body.md
gh pr comment "${PR_NUMBER}" --repo "${GH_REPO}" --body-file /tmp/comment-body.md
snapshot-notify-failure:
needs:
[snapshot, snapshot-ci, snapshot-build, snapshot-sign-macos, snapshot-smoke-test, snapshot-publish]
if: >-
always() &&
contains(needs.*.result, 'failure')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
steps:
- name: React with confused emoji
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=confused --silent
- name: Post failure comment
env:
GH_TOKEN: ${{ github.token }}
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 ''
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