Skip to content
Closed
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
156 changes: 34 additions & 122 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,139 +1,51 @@
name: Deploy Release
name: Release

on:
issue_comment:
types: [created]
workflow_dispatch:

permissions:
contents: write
pull-requests: write
issues: write
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
if: github.event.issue.pull_request != null && github.event.comment.body == 'DEPLOY-RELEASE'
release:
name: Release
runs-on: ubuntu-latest

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
issues: write
pull-requests: write
id-token: write

steps:
- name: Resolve PR metadata
run: |
PR_NUMBER="${{ github.event.issue.number }}"
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"

PR_JSON=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER)
PR_BRANCH=$(echo "$PR_JSON" | jq -r '.head.ref')
PR_STATE=$(echo "$PR_JSON" | jq -r '.state')

echo "PR_BRANCH=$PR_BRANCH" >> "$GITHUB_ENV"

if [ "$PR_STATE" != "open" ]; then
gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments \
--method POST \
--field body="Deploy cancelled: this PR is not open (state: \`$PR_STATE\`)."
exit 1
fi

- name: Check authorization
run: |
COMMENTER="${{ github.event.comment.user.login }}"

PERMISSION=$(gh api \
repos/${{ github.repository }}/collaborators/$COMMENTER/permission \
--jq '.permission' 2>/dev/null || echo "none")

IS_ADMIN=false
if [ "$PERMISSION" = "admin" ]; then
IS_ADMIN=true
fi

APPROVALS=$(gh api \
repos/${{ github.repository }}/pulls/${{ env.PR_NUMBER }}/reviews \
--jq '[.[] | select(.state == "APPROVED")] | length')

echo "Commenter: $COMMENTER | Is admin: $IS_ADMIN | Approvals: $APPROVALS"

if [ "$IS_ADMIN" = "false" ] && [ "$APPROVALS" -lt 2 ]; then
gh api repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments \
--method POST \
--field body="Deploy cancelled: **@$COMMENTER** is not a repo admin and this PR only has **$APPROVALS/2** required approvals. Either an admin must trigger the deploy, or the PR needs at least 2 approvals."
exit 1
fi

- name: Validate release branch
run: |
BRANCH="${{ env.PR_BRANCH }}"

if ! echo "$BRANCH" | grep -qE '^release/v[0-9]+\.[0-9]+\.[0-9]+$'; then
gh api repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments \
--method POST \
--field body="Deploy cancelled: branch \`$BRANCH\` does not match the required pattern \`release/vX.Y.Z\`. Only release branches may be deployed to main."
exit 1
fi

VERSION="${BRANCH#release/v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "TAG=v$VERSION" >> "$GITHUB_ENV"

- name: Merge PR into main
run: |
gh pr merge ${{ env.PR_NUMBER }} \
--merge \
--repo ${{ github.repository }}

- name: Checkout main
uses: actions/checkout@v5
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Bump package.json version
run: |
jq --arg v "${{ env.VERSION }}" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json
git add package.json
if git diff --cached --quiet; then
echo "Version already at ${{ env.VERSION }}, skipping commit."
else
git commit -m "chore(release): bump version to ${{ env.VERSION }}"
git push
fi

- name: Tag merge commit on main
run: |
git fetch origin main
git tag "${{ env.TAG }}" origin/main
git push origin "${{ env.TAG }}"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Create GitHub Release
run: |
gh release create "${{ env.TAG }}" \
--title "${{ env.TAG }}" \
--generate-notes \
--repo ${{ github.repository }}
- name: Enable Corepack
run: corepack enable

- name: Delete release branch
run: |
git push origin --delete "${{ env.PR_BRANCH }}" || true
- name: Install dependencies
run: yarn install --immutable

- name: Post success comment
if: success()
run: |
gh api repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments \
--method POST \
--field body="Deployed successfully. Version \`${{ env.VERSION }}\` has been tagged as \`${{ env.TAG }}\`, a GitHub Release has been created with auto-generated release notes, and this PR has been merged into \`main\`."
- name: Build lib
run: yarn prepare

- name: Post failure comment
if: failure()
run: |
gh api repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments \
--method POST \
--field body="Deploy failed. Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details." \
2>/dev/null || true
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: ${{ github.actor }}
GIT_AUTHOR_EMAIL: '${{ github.actor }}@users.noreply.github.com'
GIT_COMMITTER_NAME: ${{ github.actor }}
GIT_COMMITTER_EMAIL: '${{ github.actor }}@users.noreply.github.com'
run: yarn release
26 changes: 0 additions & 26 deletions .github/workflows/enforce-release-branch.yml

This file was deleted.

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"prepare": "bob build",
"typecheck": "tsc",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"test": "jest"
"test": "jest",
"release": "semantic-release"
},
"exports": {
".": {
Expand Down Expand Up @@ -38,6 +39,11 @@
"@gorhom/bottom-sheet": "^5.1.0",
"@react-native/babel-preset": "0.83.0",
"@react-native/eslint-config": "0.83.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.8",
"@semantic-release/release-notes-generator": "^14.1.1",
"@stylistic/eslint-plugin": "^5.10.0",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^29.5.14",
Expand All @@ -63,6 +69,7 @@
"react-native-safe-area-context": "^5.4.0",
"react-native-svg": "^15.15.3",
"react-test-renderer": "19.2.0",
"semantic-release": "^25.0.3",
"turbo": "^2.5.6",
"typescript": "^5.9.2"
},
Expand Down
59 changes: 59 additions & 0 deletions release.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const rules = [
{ type: 'feat', release: 'minor', title: '✨ Features' },
{ type: 'fix', release: 'patch', title: '🐛 Bug Fixes' },
{ type: 'perf', release: 'patch', title: '💨 Performance Improvements' },
{ type: 'refactor', release: 'patch', title: '🔄 Code Refactors' },
{ type: 'docs', release: 'patch', title: '📚 Documentation' },
{ type: 'chore', release: 'patch', title: '🛠️ Other changes' },
]

const sortMap = Object.fromEntries(
rules.map((rule, index) => [rule.title, index])
)

/**
* @type {import('semantic-release').GlobalConfig}
*/
module.exports = {
branches: ['main', { name: 'next', prerelease: 'next' }],
plugins: [
[
'@semantic-release/commit-analyzer',
{
preset: 'conventionalcommits',
releaseRules: [
{ breaking: true, release: 'minor' },
{ revert: true, release: 'patch' },
].concat(rules.map(({ type, release }) => ({ type, release }))),
},
],
[
'@semantic-release/release-notes-generator',
{
preset: 'conventionalcommits',
presetConfig: {
types: rules.map(({ type, title }) => ({
type,
section: title,
})),
},
writerOpts: {
commitGroupsSort: (a, z) => sortMap[a.title] - sortMap[z.title],
},
},
],
[
'@semantic-release/changelog',
{
changelogFile: 'CHANGELOG.md',
},
],
'@semantic-release/github',
[
'@semantic-release/git',
{
assets: ['package.json', 'CHANGELOG.md'],
},
],
],
}
Loading
Loading