Skip to content

Commit e979766

Browse files
Saadnajmiclaudedependabot[bot]eadronkessenma
authored
fix(0.81): backport main fixes (transforms, colors, ScrollView, text selection, CVEs) (#2876)
## Summary Backport of 10 commits from `main` to `0.81-stable`: - **fix(spm):** fix pre-existing macOS porting bugs (#2869) - **fix(transforms):** enable transforms on new arch and fix hit testing on both arches (#2866) - **chore(deps-dev):** bump undici from 5.29.0 to 6.24.0 (#2871) - **fix(fabric):** colors not respecting dark mode appearance, implement platform color (#2867) - **fix:** adjust content inset behavior for macOS (#2806) - **fix:** reduce dependency CVE overrides and refresh vulnerable transitive deps (#2874) - **feat(fabric, text):** support native text selection when selectable={true} (#2864) - **fix:** macOS ScrollView resize and content inset behavior (#2732) - **docs:** add backporting guide and automation (#2863) - **fix(fabric, textinput):** support `enableFocusRing` ### Conflict resolutions - `RCTUIView.h/m` → On 0.81-stable, `RCTUIView` lives in `React/Base/RCTUIKit.h` and `React/Base/macOS/RCTUIKit.m` (the RCTUIKit module refactor hasn't landed on 0.81). Transform changes were manually applied to these files. - `RCTUIKitCompat.h/m` → `NSColor (RCTAppearanceResolving)` category was added to `RCTUIKit.h/m` instead. - Lockfile conflicts resolved by accepting incoming CVE fixes. ## Test Plan - Same as the original PRs - Verify transforms render correctly on macOS (old + new arch) - Verify dark mode color resolution works in Fabric - Verify ScrollView resize and content inset behavior - Verify text selection with selectable={true} - Verify enableFocusRing on TextInput in Fabric 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: eadron <eadron@users.noreply.github.com> Co-authored-by: Kyle Essenmacher <15271436+kessenma@users.noreply.github.com>
1 parent b81e800 commit e979766

41 files changed

Lines changed: 3299 additions & 2269 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ai/commands/backport.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Backport to Stable Branch(es)
2+
3+
Backport the current branch's commits to one or more stable release branches.
4+
5+
## Arguments
6+
7+
The user provides one or more target stable branches as space-separated arguments (e.g., `0.81-stable` or `0.81-stable 0.82-stable`).
8+
9+
## Conventions
10+
11+
- **Branch naming:** If the current feature branch is `foo` and the target is `0.81-stable`, the backport branch is `0.81/foo`.
12+
- **PR title transformation:**
13+
- `type(scope): description` becomes `type(0.81, scope): description`
14+
- `type: description` (no scope) becomes `type(0.81): description`
15+
- The version number is extracted by stripping `-stable` from the target branch name.
16+
17+
## Steps
18+
19+
### 1. Validate
20+
21+
- Parse the arguments for one or more target branch names. If no arguments were provided, ask the user which stable branch(es) to target.
22+
- Run `git branch --show-current` to get the current branch name. Call this `FEATURE_BRANCH`.
23+
- **Refuse** if the current branch is `main` or matches `*-stable` — the user should be on a feature branch.
24+
- Verify each target branch exists on the `upstream` remote by running `git ls-remote --heads upstream <target-branch>`. If a target doesn't exist, warn the user and skip it.
25+
26+
### 2. Identify commits to cherry-pick
27+
28+
- Find the merge base: `git merge-base main HEAD`
29+
- List commits: `git log --reverse --format="%H" <merge-base>..HEAD`
30+
- If there are no commits, warn the user and stop.
31+
- Show the user the list of commits that will be cherry-picked and confirm before proceeding.
32+
33+
### 3. For each target branch
34+
35+
For each target branch (e.g., `0.81-stable`):
36+
37+
#### a. Extract version
38+
Strip the `-stable` suffix to get the version number (e.g., `0.81`).
39+
40+
#### b. Fetch and create the backport branch
41+
```bash
42+
git fetch upstream <target-branch>
43+
git checkout -b <version>/<FEATURE_BRANCH> upstream/<target-branch>
44+
```
45+
46+
If the branch `<version>/<FEATURE_BRANCH>` already exists, ask the user whether to overwrite it or skip this target.
47+
48+
#### c. Cherry-pick commits
49+
```bash
50+
git cherry-pick <commit1> <commit2> ...
51+
```
52+
53+
If a cherry-pick fails due to conflicts:
54+
- Show the user the conflicting files (`git diff --name-only --diff-filter=U`)
55+
- Read the conflicting files and help resolve the conflicts interactively
56+
- After resolution, run `git add .` and `git cherry-pick --continue`
57+
58+
#### d. Transform the PR title
59+
60+
Take the title from the most recent commit message (or ask the user for the PR title). Apply the transformation:
61+
- If it matches `type(scope): description``type(<version>, scope): description`
62+
- If it matches `type: description``type(<version>): description`
63+
64+
#### e. Push and create PR
65+
```bash
66+
git push -u origin <version>/<FEATURE_BRANCH>
67+
```
68+
69+
Then create the PR:
70+
```bash
71+
gh pr create \
72+
--repo microsoft/react-native-macos \
73+
--base <target-branch> \
74+
--title "<transformed-title>" \
75+
--body "## Summary
76+
Backport of the changes from branch \`<FEATURE_BRANCH>\` to \`<target-branch>\`.
77+
78+
## Test Plan
79+
Same as the original PR."
80+
```
81+
82+
### 4. Return to original branch
83+
84+
After processing all target branches:
85+
```bash
86+
git checkout <FEATURE_BRANCH>
87+
```
88+
89+
Report a summary of what was done: which backport PRs were created, and any that were skipped or had issues.

.claude/commands/backport.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Follow the instructions in .ai/commands/backport.md
2+
3+
Target branches: $ARGUMENTS
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
name: Backport
2+
# Creates backport PRs when someone comments "/backport <branch>" on a PR.
3+
# Also auto-updates existing backport PRs when the source PR is updated.
4+
5+
on:
6+
issue_comment:
7+
types: [created]
8+
pull_request:
9+
branches: [main]
10+
types: [synchronize]
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
# ─── Job 1: Create backport PR(s) from a /backport comment ───
17+
backport:
18+
name: Create backport
19+
if: >
20+
github.event_name == 'issue_comment' &&
21+
github.event.issue.pull_request != '' &&
22+
startsWith(github.event.comment.body, '/backport ')
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: write
26+
pull-requests: write
27+
steps:
28+
- name: Generate GitHub App token
29+
uses: actions/create-github-app-token@v2
30+
id: app-token
31+
with:
32+
app-id: ${{ vars.APP_ID }}
33+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
34+
35+
- name: React to comment
36+
env:
37+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
38+
run: |
39+
gh api \
40+
--method POST \
41+
repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
42+
-f content='+1'
43+
44+
- name: Parse target branches
45+
id: parse
46+
env:
47+
COMMENT_BODY: ${{ github.event.comment.body }}
48+
run: |
49+
# Extract everything after "/backport " and split into branch names
50+
BRANCHES=$(echo "$COMMENT_BODY" | head -1 | sed 's|^/backport ||' | xargs)
51+
if [ -z "$BRANCHES" ]; then
52+
echo "::error::No target branches specified"
53+
exit 1
54+
fi
55+
echo "branches=$BRANCHES" >> "$GITHUB_OUTPUT"
56+
57+
- name: Get PR details
58+
id: pr
59+
env:
60+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
61+
PR_URL: ${{ github.event.issue.pull_request.url }}
62+
run: |
63+
PR_DATA=$(gh api "$PR_URL")
64+
echo "number=$(echo "$PR_DATA" | jq -r '.number')" >> "$GITHUB_OUTPUT"
65+
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> "$GITHUB_OUTPUT"
66+
echo "head_branch=$(echo "$PR_DATA" | jq -r '.head.ref')" >> "$GITHUB_OUTPUT"
67+
echo "merged=$(echo "$PR_DATA" | jq -r '.merged')" >> "$GITHUB_OUTPUT"
68+
echo "state=$(echo "$PR_DATA" | jq -r '.state')" >> "$GITHUB_OUTPUT"
69+
70+
- name: Checkout
71+
uses: actions/checkout@v4
72+
with:
73+
token: ${{ steps.app-token.outputs.token }}
74+
fetch-depth: 0
75+
76+
- name: Configure git
77+
run: |
78+
git config user.name "github-actions[bot]"
79+
git config user.email "github-actions[bot]@users.noreply.github.com"
80+
81+
- name: Create backport PRs
82+
env:
83+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
84+
BRANCHES: ${{ steps.parse.outputs.branches }}
85+
PR_NUMBER: ${{ steps.pr.outputs.number }}
86+
PR_TITLE: ${{ steps.pr.outputs.title }}
87+
HEAD_BRANCH: ${{ steps.pr.outputs.head_branch }}
88+
PR_MERGED: ${{ steps.pr.outputs.merged }}
89+
REPO: ${{ github.repository }}
90+
run: |
91+
RESULTS=""
92+
ANY_FAILED=false
93+
94+
# Get commits from the PR
95+
COMMITS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/commits" --jq '.[].sha')
96+
if [ -z "$COMMITS" ]; then
97+
echo "::error::No commits found in PR #$PR_NUMBER"
98+
exit 1
99+
fi
100+
101+
for TARGET_BRANCH in $BRANCHES; do
102+
echo "::group::Backporting to $TARGET_BRANCH"
103+
104+
# Extract version from branch name (e.g., 0.81-stable -> 0.81)
105+
VERSION=$(echo "$TARGET_BRANCH" | sed 's/-stable$//')
106+
BACKPORT_BRANCH="$VERSION/$HEAD_BRANCH"
107+
108+
# Transform PR title
109+
if echo "$PR_TITLE" | grep -qP '^\w+\([^)]+\):'; then
110+
# Has scope: type(scope): desc -> type(version, scope): desc
111+
NEW_TITLE=$(echo "$PR_TITLE" | sed -E "s/^(\w+)\(([^)]+)\):/\1($VERSION, \2):/")
112+
else
113+
# No scope: type: desc -> type(version): desc
114+
NEW_TITLE=$(echo "$PR_TITLE" | sed -E "s/^(\w+):/\1($VERSION):/")
115+
fi
116+
117+
# Check if target branch exists
118+
if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH" > /dev/null 2>&1; then
119+
echo "::warning::Target branch $TARGET_BRANCH does not exist, skipping"
120+
RESULTS="$RESULTS\n- :warning: \`$TARGET_BRANCH\`: branch does not exist"
121+
ANY_FAILED=true
122+
echo "::endgroup::"
123+
continue
124+
fi
125+
126+
# Create backport branch
127+
git checkout "origin/$TARGET_BRANCH"
128+
git checkout -B "$BACKPORT_BRANCH"
129+
130+
# Cherry-pick commits
131+
CHERRY_PICK_FAILED=false
132+
for COMMIT in $COMMITS; do
133+
if ! git cherry-pick "$COMMIT" --no-edit; then
134+
CHERRY_PICK_FAILED=true
135+
git cherry-pick --abort || true
136+
break
137+
fi
138+
done
139+
140+
if [ "$CHERRY_PICK_FAILED" = true ]; then
141+
echo "::warning::Cherry-pick failed for $TARGET_BRANCH"
142+
RESULTS="$RESULTS\n- :x: \`$TARGET_BRANCH\`: cherry-pick conflicts (manual backport needed)"
143+
ANY_FAILED=true
144+
echo "::endgroup::"
145+
continue
146+
fi
147+
148+
# Push the backport branch
149+
git push -f origin "$BACKPORT_BRANCH"
150+
151+
# Check if a backport PR already exists
152+
EXISTING_PR=$(gh pr list --repo "$REPO" --head "$BACKPORT_BRANCH" --base "$TARGET_BRANCH" --json number --jq '.[0].number // empty')
153+
154+
if [ -n "$EXISTING_PR" ]; then
155+
echo "Backport PR #$EXISTING_PR already exists, updated via force-push"
156+
RESULTS="$RESULTS\n- :arrows_counterclockwise: \`$TARGET_BRANCH\`: updated existing PR #$EXISTING_PR"
157+
else
158+
# Create backport PR
159+
BACKPORT_PR_URL=$(gh pr create \
160+
--repo "$REPO" \
161+
--base "$TARGET_BRANCH" \
162+
--head "$BACKPORT_BRANCH" \
163+
--title "$NEW_TITLE" \
164+
--body "$(cat <<EOF
165+
## Summary
166+
Backport of #$PR_NUMBER to \`$TARGET_BRANCH\`.
167+
168+
## Test Plan
169+
Same as #$PR_NUMBER.
170+
EOF
171+
)")
172+
echo "Created backport PR: $BACKPORT_PR_URL"
173+
BACKPORT_PR_NUMBER=$(echo "$BACKPORT_PR_URL" | grep -oP '\d+$')
174+
RESULTS="$RESULTS\n- :white_check_mark: \`$TARGET_BRANCH\`: #$BACKPORT_PR_NUMBER"
175+
fi
176+
177+
echo "::endgroup::"
178+
done
179+
180+
# Post summary comment on the original PR
181+
COMMENT_BODY="## Backport results\n$RESULTS"
182+
gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$(echo -e "$COMMENT_BODY")"
183+
184+
if [ "$ANY_FAILED" = true ]; then
185+
echo ""
186+
echo "Some backports failed. To backport manually:"
187+
echo " 1. git fetch origin <target-branch>"
188+
echo " 2. git checkout -b <version>/<branch> origin/<target-branch>"
189+
echo " 3. git cherry-pick <commits>"
190+
echo " 4. git push -u origin <version>/<branch>"
191+
echo " 5. gh pr create --base <target-branch>"
192+
fi
193+
194+
# ─── Job 2: Auto-update backport PRs when source PR is updated ───
195+
update-backport:
196+
name: Update backport PRs
197+
if: github.event_name == 'pull_request' && github.event.action == 'synchronize'
198+
runs-on: ubuntu-latest
199+
permissions:
200+
contents: write
201+
pull-requests: write
202+
steps:
203+
- name: Generate GitHub App token
204+
uses: actions/create-github-app-token@v2
205+
id: app-token
206+
with:
207+
app-id: ${{ vars.APP_ID }}
208+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
209+
210+
- name: Find linked backport PRs
211+
id: find-backports
212+
env:
213+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
214+
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
215+
PR_NUMBER: ${{ github.event.pull_request.number }}
216+
REPO: ${{ github.repository }}
217+
run: |
218+
# Search for open PRs whose branch matches the pattern <version>/<head-branch>
219+
# e.g., if head_branch is "fix-focus", look for "*/fix-focus"
220+
BACKPORT_PRS=$(gh pr list \
221+
--repo "$REPO" \
222+
--state open \
223+
--json number,headRefName,baseRefName \
224+
--jq ".[] | select(.headRefName | test(\"^[0-9]+\\\\.[0-9]+/$HEAD_BRANCH\$\")) | \"\(.number) \(.headRefName) \(.baseRefName)\"")
225+
226+
if [ -z "$BACKPORT_PRS" ]; then
227+
echo "No backport PRs found for branch $HEAD_BRANCH"
228+
echo "found=false" >> "$GITHUB_OUTPUT"
229+
else
230+
echo "Found backport PRs:"
231+
echo "$BACKPORT_PRS"
232+
echo "found=true" >> "$GITHUB_OUTPUT"
233+
# Write to a file to handle multiline
234+
echo "$BACKPORT_PRS" > /tmp/backport-prs.txt
235+
fi
236+
237+
- name: Checkout
238+
if: steps.find-backports.outputs.found == 'true'
239+
uses: actions/checkout@v4
240+
with:
241+
token: ${{ steps.app-token.outputs.token }}
242+
fetch-depth: 0
243+
244+
- name: Configure git
245+
if: steps.find-backports.outputs.found == 'true'
246+
run: |
247+
git config user.name "github-actions[bot]"
248+
git config user.email "github-actions[bot]@users.noreply.github.com"
249+
250+
- name: Update backport PRs
251+
if: steps.find-backports.outputs.found == 'true'
252+
env:
253+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
254+
PR_NUMBER: ${{ github.event.pull_request.number }}
255+
REPO: ${{ github.repository }}
256+
run: |
257+
# Get current commits from the source PR
258+
COMMITS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/commits" --jq '.[].sha')
259+
260+
while IFS= read -r LINE; do
261+
BP_NUMBER=$(echo "$LINE" | awk '{print $1}')
262+
BP_BRANCH=$(echo "$LINE" | awk '{print $2}')
263+
BP_BASE=$(echo "$LINE" | awk '{print $3}')
264+
265+
echo "::group::Updating backport PR #$BP_NUMBER ($BP_BRANCH -> $BP_BASE)"
266+
267+
# Reset the backport branch to the target stable branch
268+
git checkout "origin/$BP_BASE"
269+
git checkout -B "$BP_BRANCH"
270+
271+
# Re-cherry-pick all commits
272+
CHERRY_PICK_FAILED=false
273+
for COMMIT in $COMMITS; do
274+
if ! git cherry-pick "$COMMIT" --no-edit; then
275+
CHERRY_PICK_FAILED=true
276+
git cherry-pick --abort || true
277+
break
278+
fi
279+
done
280+
281+
if [ "$CHERRY_PICK_FAILED" = true ]; then
282+
echo "::warning::Cherry-pick failed while updating backport PR #$BP_NUMBER"
283+
gh pr comment "$PR_NUMBER" --repo "$REPO" \
284+
--body ":warning: Failed to auto-update backport PR #$BP_NUMBER to \`$BP_BASE\` due to conflicts. Manual update needed."
285+
gh pr comment "$BP_NUMBER" --repo "$REPO" \
286+
--body ":warning: Auto-update from source PR #$PR_NUMBER failed due to cherry-pick conflicts. Manual update needed."
287+
else
288+
git push -f origin "$BP_BRANCH"
289+
echo "Successfully updated backport PR #$BP_NUMBER"
290+
fi
291+
292+
echo "::endgroup::"
293+
done < /tmp/backport-prs.txt
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
__default__: patch
3+
---
4+
5+
Backport fixes from main: transforms, dark mode colors, ScrollView resize, text selection, CVEs

0 commit comments

Comments
 (0)