Skip to content
Open
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
106 changes: 106 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Release

# Publishes to npm automatically once `Build` has passed on master.
#
# The version and changelog come from release-it + @release-it/conventional-changelog
# (see the "release-it" block in package.json), so they are derived from the
# conventional commit messages since the previous tag.
#
# Requires an `NPM_TOKEN` repository secret holding an npm access token with
# publish rights on @react-native-menu/menu.
on:
workflow_run:
workflows: ["Build"]
types:
- completed
branches:
- master
# Manual escape hatch, e.g. to retry a release that failed partway through.
# Note this path does not check Build, so only run it on a commit you know is green.
workflow_dispatch:

concurrency:
group: release
cancel-in-progress: false

jobs:
release:
name: Publish to npm
runs-on: ubuntu-latest
# Never react to the `chore: release` commit release-it pushes itself,
# and never publish off a red Build.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
!startsWith(github.event.workflow_run.head_commit.message, 'chore: release'))
permissions:
# release-it pushes the release commit and tag, and creates the GitHub release.
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: master
# Full history and tags, so conventional-changelog can see the commits
# since the previous release.
fetch-depth: 0

- name: Decide whether to release
id: guard
run: |
set -euo pipefail

# `Build` validated a specific commit. If master has moved on since then,
# publishing now would ship code CI never checked.
if [ "${{ github.event_name }}" = "workflow_run" ]; then
expected='${{ github.event.workflow_run.head_sha }}'
actual="$(git rev-parse HEAD)"
if [ "$expected" != "$actual" ]; then
echo "master moved past the commit Build validated ($expected -> $actual)."
echo "Skipping; the next green Build will pick this up."
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi

last_tag="$(git describe --tags --abbrev=0 2>/dev/null || true)"
if [ -n "$last_tag" ]; then
range="$last_tag..HEAD"
else
range="HEAD"
fi

# Only feat/fix/perf and breaking changes produce a version bump under the
# angular preset. Without one, release-it has nothing to do.
releasable="$(git log "$range" --format='%s%n%b' \
| grep -cE '^(feat|fix|perf)(\([^)]*\))?!?: |^BREAKING[ -]CHANGE:' || true)"

echo "Releasable commits since ${last_tag:-the start of history}: $releasable"
if [ "$releasable" -eq 0 ]; then
echo "release=false" >> "$GITHUB_OUTPUT"
else
echo "release=true" >> "$GITHUB_OUTPUT"
fi

- uses: actions/setup-node@v4
if: steps.guard.outputs.release == 'true'
with:
node-version: 22
registry-url: https://registry.npmjs.org

- name: Install dependencies
if: steps.guard.outputs.release == 'true'
run: yarn install --immutable

- name: Configure git identity
if: steps.guard.outputs.release == 'true'
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'

- name: Release
if: steps.guard.outputs.release == 'true'
run: yarn release --ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# setup-node writes an .npmrc that reads NODE_AUTH_TOKEN.
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ When you're sending a pull request:
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
- For version-dependent changes, follow the versioning structure for `MenuViewManager` outlined in the **Versioning in `MenuViewManager`** section. Ensure all version-specific files are included in `reactNativeVersionPatch` and referenced in `build.gradle`.

### Releasing

Releases are automated. Merging to `master` is all that is needed — no one runs `yarn release` locally.

`.github/workflows/release.yml` waits for the `Build` workflow to finish on `master` and then, if it passed, runs `release-it` in CI. `@release-it/conventional-changelog` derives the new version and the release notes from the commit messages since the previous tag, so **the version bump is decided entirely by your commit message**:

- `fix:` or `perf:` → patch
- `feat:` → minor
- `!` after the type, or a `BREAKING CHANGE:` footer → major

A push containing only `chore:`, `docs:`, `test:`, or `refactor:` commits releases nothing, and the workflow stops before touching npm. Note that nothing currently enforces the commit format, so a `feat` mislabelled as `chore` silently ships nothing — squash-merge titles matter here.

The workflow publishes to npm, pushes the `chore: release <version>` commit and the `v<version>` tag, and creates the GitHub release. It authenticates with an `NPM_TOKEN` repository secret; if publishing starts failing with an auth error, that token has most likely expired.

Two things the workflow deliberately refuses to do:

- It skips if `master` has moved past the commit `Build` validated, rather than publishing code CI never checked. The next green `Build` picks it up.
- It ignores its own `chore: release` commit, so a release cannot trigger another release.

`Release` can also be started manually from the Actions tab, which is useful when a release fails partway through. That path skips the `Build` gate, so only use it on a commit you know is green.

## Code of Conduct

### Our Pledge
Expand Down
2 changes: 1 addition & 1 deletion example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# particularly useful for configuring JVM memory settings for build performance.
# This does not affect the JVM settings for the Gradle client VM.
# The default is `-Xmx512m -XX:MaxMetaspaceSize=256m`.
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute
# projects in parallel. To learn more about parallel task execution, see the
Expand Down
Loading