mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
latest
41137
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a59eff64fa |
Release 0.87.1
#publish-packages-to-npm&latestlatest v0.87.1 |
||
|
|
1a6b526d0e | [0.87] Regenerate ReactNativeApi snapshot with CI toolchain | ||
|
|
dda5dd3846 | [0.87] Regenerate ReactNativeApi snapshot | ||
|
|
476887c8d5 | Merge remote-tracking branch 'refs/remotes/origin/pr/58052' into 0.87-stable | ||
|
|
e6e404eb6c |
Hold an app's spm.modules names to the same rules as a library's (#58060)
Summary:
An app declares extra native modules through spm.modules in its react-native.config.js. Those names went into the generated package graph unvalidated, and two of the ways they can go wrong fail silently.
This PR fixes this by using the same Swift name collision detection/resolving as we introduced in https://github.com/react/react-native/issues/58044
> **NOTE:** https://github.com/react/react-native/issues/58044 must be merged before this one so that we can change the base branch for this one to `main`
## Changelog:
[IOS] [FIXED] - Reject colliding or invalid spm.modules names instead of silently dropping a module from the build
Pull Request resolved: https://github.com/react/react-native/pull/58060
Test Plan: ✅ Unit tests/CI
Reviewed By: mdvacca
Differential Revision: D117360298
Pulled By: cipolleschi
fbshipit-source-id: d2531d51b6d371cf6bc98dc85c9071ee81fb7a37
(cherry picked from commit
|
||
|
|
c9511295ad |
Resolve SwiftPM manifest naming collisions (#58044)
Summary:
A dependency's Swift name is derived from its npm package name with the scope dropped, which makes two collisions unavoidable: `powersync/react-native` derives `ReactNative`, one of React Native's own names, and `a/foo` and `b/foo` both derive `Foo`.
Either was emitted into the package graph as-is, and SwiftPM then failed deep inside dependency resolution with a duplicate-name error that named neither the library nor the react-native.config.js that caused it.
This was seen in the PR here https://github.com/powersync-ja/powersync-js/pull/1076 - and was hard to fix.
This PR fixes this by adding support for prefixing the name with the scope if the name without scope crashes.
In addition the same logic is added when two packages have names that collide with each other.
If the name given by the SwiftPM scaffolder doesn't work for you - you can use the `spm.name` field in the `react-native.config.js` file to set a specific name (see SwiftPM docs).
[IOS] [FIXED] - Resolve Swift manifest naming collisions
Pull Request resolved: https://github.com/react/react-native/pull/58044
Test Plan: ✅ Unit tests green
Reviewed By: mdvacca, cortinico
Differential Revision: D117178818
Pulled By: cipolleschi
fbshipit-source-id: 23269f4d959b84ac484be1911e8a863c74008ff9
(cherry picked from commit
|
||
|
|
33624ceda4 |
Honor a library's spm.name when scaffolding its Package.swift (#58033)
Summary:
The scaffolder derived every target name with `toSwiftName(dep.name)`, ignoring the `spm.name` override in the library's own react-native.config.js.
A library setting that override got a manifest whose product name differed from the name the autolinker registers it under, and SPM failed resolution on `.product(name: "X", package: "X")` — the exact mismatch the override exists to prevent.
This setting is documented - but not followed up in the implementation.
Use the name the autolinker already resolved (`dep.swiftName`), falling back to `toSwiftName` when a caller runs the translation without it. Sibling references resolve the same way, through the new `SpmScaffoldSpec.siblingSwiftNames`.
Fixed after community feedback here: https://github.com/powersync-ja/powersync-js/pull/1076
## Changelog:
[IOS] [FIXED] - Fixed not reading spm.name from react-native.config.js when scaffolding the Swift manifest
Pull Request resolved: https://github.com/react/react-native/pull/58033
Test Plan: ✅ unit tests
Reviewed By: zeyap
Differential Revision: D116794063
Pulled By: cipolleschi
fbshipit-source-id: 8b2f204efbc4d7537839ccef5b90cf4eba0d27bf
(cherry picked from commit
|
||
|
|
617a82ff29 |
Fail with clear error message when a dep depends on an autolinking plugin host (#58087)
Summary:
A library can ship with its own SwiftPM autolinking plugin, making the RN autolinker skip generating a target for it since the plugin now owns its native contribution.
If another library depends on a library that contains such a plugin, we currently just fail without any explanation to why and how we can fix this.
This PR diagnoses this at the declaration level, checking if a dependency is an existing target or not - emitting a clear error about what happens and why it happens.
## Changelog:
[IOS] [FIXED] - Added hard fail and clear error message when an autolinking plugin host is referenced as a dependency
Pull Request resolved: https://github.com/react/react-native/pull/58087
Test Plan: ✅ Unit tests passes
Reviewed By: Abbondanzo
Differential Revision: D117196705
Pulled By: cipolleschi
fbshipit-source-id: fa7865ec6a5c07232e67240d322c315dad570d17
(cherry picked from commit
|
||
|
|
5e24a6952f |
Drop the inert publicHeadersPath from spm.modules (#58059)
Summary:
When using `react-native.config.js` to declare app-side modules using the `spm.modules` field, there is a field for providing the public header files for a module which is not used by the code. This field does not have any meaning either, a local app-side module is registered through module discovery anyway.
This PR removes this field from the SPM config.
## Changelog:
[IOS] [FIXED] - Removed unused field spm.modules.publicHeaderFiles from react-native.config.js's spm section
Pull Request resolved: https://github.com/react/react-native/pull/58059
Test Plan: ✅ Unit tests/CI
Reviewed By: christophpurrer
Differential Revision: D116935666
Pulled By: cipolleschi
fbshipit-source-id: 78c9576cb6bd9996a4972ba6f38f21bb4743d4af
(cherry picked from commit
|
||
|
|
40af900182 |
Read both export styles from a library's react-native.config.js (#58034)
Summary:
When saving settings in `react-native.config.js`, we should support all styles of exported data:
```js
module.exports = { spm: { name: 'worklets' } }; // old style
export const spm = { name: 'worklets' }; // new style, with a label
export default { spm: { name: 'worklets' } }; // new style, no label — "just this"
```
We only read the two first in the SwiftPM pipeline, silently ignoring anything written with `export default`.
This PR fixes this by adding a catch that tries to decode the default export.
Fixed after community feedback here: https://github.com/powersync-ja/powersync-js/pull/1076
[IOS] [FIXED] - SwiftPM pipeline now reads react-native.config.js with default exports correctly
Pull Request resolved: https://github.com/react/react-native/pull/58034
Test Plan: ✅ Added and ran unit tests.
Reviewed By: christophpurrer
Differential Revision: D116935856
Pulled By: cipolleschi
fbshipit-source-id: 7ebf7af3b3aaee2ffb506f94a15fabc244a4db30
(cherry picked from commit
|
||
|
|
cdb180ce4f |
Derive the generated SPM manifests from shared name constants (#58035)
Summary:
After community feedback from this one https://github.com/powersync-ja/powersync-js/pull/1076 we've decided to fix the issue of clashing name constants - to avoid the SwiftPM pipeline scaffolding packages that collides with built-in packages.
This PR is the first step fixing this - refactoring the names of internal packages into constants.
## Changelog:
[IOS] [FIXED] - Created constants for internal SwiftPM packages so that we can create guards to avoid collisions
Pull Request resolved: https://github.com/react/react-native/pull/58035
Test Plan: ✅ Unit tests
Reviewed By: Abbondanzo
Differential Revision: D116936194
Pulled By: cipolleschi
fbshipit-source-id: 2882e512489cc4784a09aeb03769e97d64d286f7
(cherry picked from commit
|
||
|
|
f4cdbc403f |
docs(ios-prebuild): correct how SwiftPM consumes the prebuilt React headers (#58007)
Summary:
`ios-prebuild/__docs__/README.md` said `React.framework`'s headers-spec layout
"is what both CocoaPods and SwiftPM consume". That is right for CocoaPods and
misleading for SwiftPM: `React.xcframework` is not a member of the Swift package
graph at all, so nothing on the SwiftPM side reads its framework module map.
What actually happens is a staging step on the consumer side —
`stageReactHeadersTarget` in `scripts/spm/flavored-frameworks.js` copies
`React.framework/Headers` into `ReactHeadersTarget/include/React` and rewrites
`framework module React` to a plain `module React`, which is then vended as the
`ReactHeaders` target. The prebuild output is still the source of those headers,
which is why the sentence was nearly right; the consumption path is what differs.
Says so, and keeps the CocoaPods half explicit about `FRAMEWORK_SEARCH_PATHS` so
the two paths read as the distinct mechanisms they are.
## Changelog:
[Internal] - Clarify that SwiftPM consumes the prebuilt React headers through a
staged `ReactHeaders` target, not through the XCFramework's module map
Pull Request resolved: https://github.com/react/react-native/pull/58007
Test Plan:
Docs only. Verified against `scripts/spm/generate-spm-package.js`, whose generated
`ReactNative` manifest declares exactly three headers-only products and no runtime
`binaryTarget`, and against `stageReactHeadersTarget`, which does the copy and the
module-map rewrite only after Debug and Release are asserted to expose identical
headers. Prettier clean — the file was formatted before this change and still is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reviewed By: fabriziocucci
Differential Revision: D116598314
Pulled By: cipolleschi
fbshipit-source-id: 3b58101e64410d35f0346c696992dda8261e00a4
(cherry picked from commit
|
||
|
|
115a9ecc26 |
docs(swiftpm): rename __doc__ to __docs__, drop two superseded docs, correct the rest (#58006)
Summary:
Rebased onto `main` after https://github.com/react/react-native/issues/57757, https://github.com/react/react-native/issues/57756, https://github.com/react/react-native/issues/57762 and https://github.com/react/react-native/issues/57744 landed. Because this
PR renames `__doc__` and Prettier-formats every file, a hunk-level rebase was the
wrong tool — it stopped on the second of nine commits and the reformat commit then
conflicted with everything after it. So the history is **replayed** on top of the
new `main` instead, and reordered to put Prettier *before* the content edits, which
makes the substantive diff reviewable rather than tangled with reflow.
Verified nothing those four PRs added was lost: every heading on `main` survives
except the two this PR deliberately renames, and their facts — the
`artifactsVersionOverride` pin, the autolinking-config-command pin, the
pre-injection build-setting values, and the promoted-scalar caveat — are carried
into the new "Files the tool touches" table rather than sitting in a row this PR
deletes.
Housekeeping and accuracy pass over `packages/react-native/scripts/spm/__docs__`.
Docs only — no code or test changes.
**Rename.** `scripts/spm/__doc__` was the only `__doc__` directory in the repo;
every other subsystem uses `__docs__` (27+ directories, and the convention
documented in `__docs__/GUIDELINES.md`). The misspelling also meant these files
were **published to npm**: `package.json` includes `scripts/spm` in `files` and
excludes `!**/__docs__/**`, which the misspelled directory dodged.
**Removed two superseded documents.** `rfc-spm-xcframework.md` (707 lines) was a
stale ancestor of RFC0994, still describing `spm init`, xcodeproj *generation*, the
`.xcodeproj.legacy` rename migration, a `spm clean` command that does not exist,
stub `Package.swift` files and VFS overlays. `spm-plugins-assessment.md` evaluated
SwiftPM plugins as a replacement for the injected build phases — conclusion valid,
detail stale (it listed a "Prepare VFS Overlay" phase). Both have had their durable
content folded into RFC0994.
**Corrected the remaining docs against the implementation.** The substantive one:
auto-sync is **two** hooks, not one — a scheme pre-action in the app's *shared*
scheme plus the build phase. The docs described only the phase.
Two claims here are corrections of my own earlier drafts, made after testing them
on `private/helloworld` rather than reading the code:
- Neither hook can bootstrap a clean checkout. With `build/` deleted,
`xcodebuild -scheme … build` fails in nine lines of log, `Resolve Package Graph`
first, the pre-action never running. The one-time setup run really is required,
and the ordering table now shows the measured sequence.
- A sync failure is not unconditionally non-fatal. Exit 2 — a dependency with no
`Package.swift` — maps to `exit 1` and fails the build deliberately; only other
non-zero codes warn. As written, the doc also contradicted the exit-2 behaviour
documented elsewhere in the same file.
Smaller fixes: `hermesvm` → `hermes-engine` (that name is in no code), plugin
`watchPaths` added to the staleness inputs, and the `#auto-sync-build-phase`
anchors left dangling by the heading rename.
**Added a `__docs__/README.md` index**, per `GUIDELINES.md`, plus a **"Files the
tool touches"** table — there was no single answer to what the tool creates or
modifies. Writing it surfaced that `add` creates or appends to `ios/.gitignore`
(documented nowhere), and that `deinit` does not revert that block, anything
`--deintegrate` changed, or a promoted array setting — so the "exact inverse of
`add`" claim needed qualifying in three places.
**Documented `spm.dependencies`**, the SwiftPM analog of a podspec's
`s.dependency`, which was implemented but absent from every doc, and corrected the
scaffold prerequisite: it is `add`/`update` that stops, with a distinct exit code 2,
not the build.
**Prettier-formatted the docs, in its own commit.** These were the only unformatted
markdown files in the repository, so `prettier --list-different "./**/*.md"` goes
from three failures to clean.
## Changelog:
[Internal] - SwiftPM docs: rename `__doc__` to `__docs__`, remove two superseded
documents, and correct the rest against the implementation
Pull Request resolved: https://github.com/react/react-native/pull/58006
Test Plan:
- `npx prettier --check "packages/react-native/scripts/spm/__docs__/*.md"` passes;
`npx prettier --list-different "./**/*.md"` is empty for the whole repo.
- All 19 intra-doc anchor links verified against GitHub's slug rules; none dead.
Every relative path out of the new README resolves, including the root-index hop.
- `npm pack --dry-run --ignore-scripts` lists no `scripts/spm/__docs__/` entries
while every `scripts/spm/*.js` still ships.
- The Prettier commit is content-neutral: comparing `[A-Za-z0-9]+` token streams
across it, all three files are identical word for word.
- Behavioural claims traced to source: `VALID_ACTIONS` in `setup-apple-spm.js`;
`injectOrCreateScheme` / `addPreActionToScheme` and the `RC -eq 2` branch of
`buildSyncAutolinkingScript` in `generate-spm-xcodeproj.js`; `BUILTIN_FRAMEWORKS`
in `flavored-frameworks.js`; `generateXCFrameworksPackageSwift` in
`generate-spm-package.js`; `ensureGitignoreSpmEntries` and its `action =--sanitized--
Reviewed By: fabriziocucci
Differential Revision: D116598871
Pulled By: cipolleschi
fbshipit-source-id: b1822dcc885b9c882ec3a2626addc42553244b51
(cherry picked from commit
|
||
|
|
5ad5425914 |
- Keep the quotes on header search paths (#57981)
Summary:
a regression from https://github.com/react/react-native/commit/a8156acf8bbc9ee15901cf0d8935d73454768aa7 and breaks react-native nightly build at expo: https://github.com/expo/expo/actions/runs/31990917142/job/95274214849
the `shellsplit` will stripe quotes and break paths with spaces like `Swift Compatibility Header`. this pr tries to re-quote.
here's small demo script
```ruby
require 'shellwords'
# What main does today: shellsplit the existing value, append, write it back.
def main_behaviour(existing, add)
paths = existing || []
paths = Shellwords.shellsplit(paths) if paths.is_a?(String)
(paths + add).uniq
end
# What this PR does: quote only the paths we add, never touch what is there.
def this_pr(existing, add)
quoted = add.map { |path| "\"#{path}\"" }
case existing
when nil
quoted
when Array
existing + quoted.reject { |path| existing.include?(path) }
else
([existing] + quoted.reject { |path| existing.include?(path) }).join(" ")
end
end
# Xcode joins an Array setting with spaces, then splits it on whitespace while
# honouring quotes. This is what the compiler ends up with.
def as_xcode_reads_it(value)
Shellwords.shellsplit(value.is_a?(Array) ? value.join(" ") : value)
end
ADD = ['$(PODS_ROOT)/ReactNativeDependencies/Headers']
# A pod that exports a Swift compatibility header. The directory name has
# spaces, so the podspec quotes it. Podspecs write this as a String or as an
# Array, and both forms reach add_rn_third_party_dependencies.
SWIFT_HEADER = '${PODS_CONFIGURATION_BUILD_DIR}/MyPod/Swift Compatibility Header'
CASES = {
'String' => "\"$(PODS_ROOT)/DoubleConversion\" \"#{SWIFT_HEADER}\"",
'Array' => ['"$(PODS_ROOT)/DoubleConversion"', "\"#{SWIFT_HEADER}\""],
}
CASES.each do |label, existing|
puts "#{label} HEADER_SEARCH_PATHS"
{ 'main' => method(:main_behaviour), 'this PR' => method(:this_pr) }.each do |name, fn|
paths = as_xcode_reads_it(fn.call(existing, ADD))
verdict = paths.include?(SWIFT_HEADER) ? 'ok' : 'BROKEN'
puts " #{name.ljust(7)} -> #{paths.size} paths, #{verdict}: #{paths.inspect}"
end
puts
end
```
output
```
String HEADER_SEARCH_PATHS
main -> 5 paths, BROKEN: ["$(PODS_ROOT)/DoubleConversion", "${PODS_CONFIGURATION_BUILD_DIR}/MyPod/Swift", "Compatibility", "Header", "$(PODS_ROOT)/ReactNativeDependencies/Headers"]
this PR -> 3 paths, ok: ["$(PODS_ROOT)/DoubleConversion", "${PODS_CONFIGURATION_BUILD_DIR}/MyPod/Swift Compatibility Header", "$(PODS_ROOT)/ReactNativeDependencies/Headers"]
Array HEADER_SEARCH_PATHS
main -> 3 paths, ok: ["$(PODS_ROOT)/DoubleConversion", "${PODS_CONFIGURATION_BUILD_DIR}/MyPod/Swift Compatibility Header", "$(PODS_ROOT)/ReactNativeDependencies/Headers"]
this PR -> 3 paths, ok: ["$(PODS_ROOT)/DoubleConversion", "${PODS_CONFIGURATION_BUILD_DIR}/MyPod/Swift Compatibility Header", "$(PODS_ROOT)/ReactNativeDependencies/Headers"]
```
## Changelog:
[IOS][FIXED] - Keep the quotes on header search paths containing spaces in `add_rn_third_party_dependencies`
Pull Request resolved: https://github.com/react/react-native/pull/57981
Test Plan: apply the patch on create-expo-nightly and the ios build should pass https://github.com/expo/expo/pull/49023
Reviewed By: javache
Differential Revision: D116600347
Pulled By: cipolleschi
fbshipit-source-id: 73c3c7560e5c4970b678298c05743d1e2404cc9b
(cherry picked from commit
|
||
|
|
11ef67b94d |
Type Animated.Value.addListener callback (#57992)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57992
Addresses a type narrowness regression (Strict API vs legacy manual types), raised by user feedback:
https://github.com/react-native-community/discussions-and-proposals/discussions/1015#discussioncomment-18053943
Changelog:
[General][Fixed] - **Animated**: `Animated.Value.addListener()` types its callback payload as `{value: number}` rather than `any`
Reviewed By: vzaidman
Differential Revision: D116440622
fbshipit-source-id: b85c7d7fbd8e81cc2e953c9a6f60701f3991ba6e
(cherry picked from commit
|
||
|
|
a1c3cfe630 |
Type Animated.event() as an event handler instead of any (#57991)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57991
Addresses a type narrowness regression (Strict API vs legacy manual types), raised by user feedback:
https://github.com/react-native-community/discussions-and-proposals/discussions/1015#discussioncomment-18053943
Changelog:
[General][Fixed] - **Animated**: `Animated.event()` is typed as an event handler rather than `any`
Reviewed By: vzaidman
Differential Revision: D116440620
fbshipit-source-id: 4ba629c511fcf28981700d1bc841a4ce8dcf17f8
(cherry picked from commit
|
||
|
|
f473b0367b |
fix(swiftpm): fix two ways array build settings were mishandled (#57744)
Summary:
Two bugs in `addArrayStringValues`, which adds members to an array build setting (`HEADER_SEARCH_PATHS`, `OTHER_LDFLAGS`, `FRAMEWORK_SEARCH_PATHS`, `LD_RUNPATH_SEARCH_PATHS`). Both hit real projects; neither was visible from the existing fixture.
**1. A promoted scalar was never restored.** A setting that already exists as a scalar gets promoted to a `( … )` array, but that was recorded as a plain member-append — so `deinit` stripped the members and left the array shell plus its injected `"$(inherited)"` behind. Stock Xcode projects hit this: the app template sets `LD_RUNPATH_SEARCH_PATHS = "$(inherited) executable_path/Frameworks";` as a target-level scalar.
Fixed by pinning the pre-injection value in `.spm-injected.json` and restoring it in place. It is stored raw (a bare scalar's token runs to the `;`, carrying whitespace that must come back), recorded only if the merge actually changed the field, and not restored if the field is gone.
**2. A one-line array was corrupted.** The append anchored on `lastIndexOf('\n', tokenEnd - 1)`, which assumes multi-line. With no newline in the value that lands on the *previous* line, so members were spliced above the field, outside the array:
```
{
"/new", ← bare entry in the dict body: invalid pbxproj
HEADER_SEARCH_PATHS = ("/vendor", ); ← member never added
}
```
The result is a project Xcode cannot open, and `deinit` could not remove the stray line. Xcode writes multi-line arrays, but hand-edited projects and other generators (XcodeGen, Tuist) emit compact ones. Fixed by splicing inline ahead of the `)`; removal gained matching delimiter-anchored patterns, so the span removed is the span inserted. The dedupe parse was also quote-blind — a member holding a quoted comma parsed as two tokens — and is now quote-aware.
**Tradeoff:** reversing a promotion rewrites the whole field, so members hand-added to a promoted array afterwards are lost.
**Rebase note:** `main` has since grown an overlapping guard (`buildSettingValueTokens`) that skips the append when every value is already present, avoiding the *no-op* promotion. It is kept and complements this change: main still promotes irreversibly when there *is* a fresh value to add to a scalar, which is what the restore here covers. The two records stay mutually exclusive per key, pinned by a test.
## Changelog:
[Internal] [Fixed] - SwiftPM: `spm deinit` restores a promoted scalar build setting, and `spm add` no longer corrupts a one-line array
Pull Request resolved: https://github.com/react/react-native/pull/57744
Test Plan:
`yarn jest packages/react-native/scripts` → **963 tests, 32 suites** green; eslint, prettier and flow clean.
Written red first: byte-identical `add` → `deinit` round-trips for each pre-existing shape (absent, multi-line, bare and quoted scalars, and the one-line forms), plus `add` → `update` → `deinit`. The multi-line path is unchanged byte-for-byte, verified by a differential harness over 48 add/remove cases against the previous implementation.
Re-verified after the rebase on the committed `HelloWorld.xcodeproj`, driving the real `injectSpmIntoExistingXcodeproj` / `removeSpmInjection`. On `main` a one-line `HEADER_SEARCH_PATHS` gains a bare `"…/autolinking/headers",` entry above the field and never receives the member; with this change it lands inside the array, and a pre-existing scalar comes back exactly.
Reviewed By: fabriziocucci
Differential Revision: D114317839
Pulled By: cipolleschi
fbshipit-source-id: 08bc81f80855721bf2123143063462109a99ac25
(cherry picked from commit
|
||
|
|
f0800d9ec4 |
Set SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG when injecting SPM (#57914)
Summary:
Swift's `#if DEBUG` is gated by SWIFT_ACTIVE_COMPILATION_CONDITIONS, not by GCC_PREPROCESSOR_DEFINITIONS (which only reaches C/ObjC/C++). The app template does not commit that setting; CocoaPods injects it at `pod install` time (react_native_post_install -> set_build_setting
SWIFT_ACTIVE_COMPILATION_CONDITIONS = ["$(inherited)", "DEBUG"] on Debug).
An app set up with the experimental SwiftPM support never runs CocoaPods, so `#if DEBUG` is false even in a Debug build: AppDelegate.swift's `bundleURL()` skips the Metro URL and falls back to a main.jsbundle a Debug build never produced, and the app dies at launch with "No script url provided ... unsanitizedScriptURLString = (null)" while Metro is running.
Info: https://github.com/react-native-community/template/pull/244
## Changelog:
Inject the setting from `spm add`/`update` alongside the other React build settings, into debug-flavored configurations only (the same flavorForBuildConfiguration test that selects the debug xcframeworks), so a config linking the debug binaries also compiles its Swift with DEBUG.
<!-- Help reviewers and the release process by writing your own changelog entry.
Pick one each for the category and type tags:
[IOS] [FIXED] - Set SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG when injecting SPM
For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
Pull Request resolved: https://github.com/react/react-native/pull/57914
Test Plan:
1. Build spm-only rn app in debug
- App will fail to connect to the Metro
2. Apply changes and run `react-native spm`
3. Build again
- App will work as expected in debug and Metro will be connected
Reviewed By: Abbondanzo
Differential Revision: D115736551
Pulled By: cipolleschi
fbshipit-source-id: 1e42ca26ac1992bfa827ce85a63cf4564d313e15
(cherry picked from commit
|
||
|
|
6e007c30cb |
fix: make an interrupted React-Core-prebuilt swap recoverable (#57831)
Summary:
`replace-rncore-version.js` writes `.last_build_configuration` only after it has already replaced `React.xcframework`, so a build cancelled between those two steps leaves the marker naming a flavor that is no longer on disk, and when the marker is absent entirely the script assumes the on-disk flavor is Debug. Either state makes the next build for that configuration take the "no need to replace" path and link against the other configuration's core, which fails with undefined C++ symbols for `Props`, `DebugStringConvertible` and the Fabric vtables, and nothing corrects it afterwards because the pod directory survives a clean.
This change writes the marker before the framework is touched, using a value that is not a valid configuration, so a later run can tell that the previous swap did not finish and replaces again. The fresh install path now also records the configuration it assumed instead of leaving that state implicit, which is sound because a swap can no longer leave the marker missing.
Fixes https://github.com/react/react-native/issues/57598.
## Changelog:
[IOS] [FIXED] - Recover React-Core-prebuilt configuration swaps that were interrupted before the marker was updated
Pull Request resolved: https://github.com/react/react-native/pull/57831
Test Plan:
Four new cases in `packages/react-native/scripts/__tests__/replace-rncore-version-test.js`, driven through the real CLI entry point the podspec `[RNCore]` build phase uses, so no new export was needed and the production diff is logic only. Against unmodified upstream two of them fail, one with `ENOENT` on `.last_build_configuration` because the skip path never wrote the marker, and one showing the marker still naming the stale flavor after a failed swap. With the change the file is 8 passed, including all 4 pre-existing tests. `prettier --check` is clean on both files.
The actual iOS link failure needs a prebuilt-core CocoaPods install and an Xcode build, so that was not reproduced locally.
Reviewed By: cipolleschi
Differential Revision: D114900617
Pulled By: fabriziocucci
fbshipit-source-id: 5d7f9b7be9cf1dd1728daa1e98ff51b87b924d2c
(cherry picked from commit
|
||
|
|
1216c1b651 |
feat(swiftpm): let autolinking plugins declare build-time script phases (#57757)
Summary:
**SwiftPM has no equivalent of CocoaPods' `script_phase`, so a framework that generates content at build time can't get one.** The first casualty is expo-constants: nothing writes `EXConstants.bundle/app.config`, which shows up at runtime as *"Unable to find the embedded app config"*.
This adds a 6th field to the SwiftPM autolinking plugin contract:
```js
scriptPhases: [{
id: 'expo-constants.app-config', // stable: ledger key + deterministic UUID seed
name: 'Generate Expo app.config', // Xcode's display name
script: '…',
position: 'end', // 'end' (default) | 'beforeCompile'
inputPaths: ['$(SRCROOT)/../app.json'],
outputPaths: ['$(TARGET_BUILD_DIR)/…/EXConstants.bundle/app.config'],
alwaysOutOfDate: true,
}]
```
The plugin returns data; RN validates it, records it to a `.spm-plugin-script-phases.json` sidecar (written even when empty, so removing a plugin clears stale entries), and `spm add`/`update` emits one `PBXShellScriptBuildPhase` per entry — tracked in `.spm-injected.json` by `id`, so `update` reconciles and `deinit` reverts.
On a real Expo app, against a local cut of this branch. A `position: 'end'` phase lands last, after the JS bundle phase:
```
5. Resources
6. Bundle React Native code and images
7. [Expo Dev Launcher] Strip Local Network Keys for Release
8. Generate Expo app.config ← last
```
`BUILD SUCCEEDED`, and `EXConstants.bundle/app.config` is written with `sdkVersion: 56.0.0` — precisely the value whose absence caused the original bug. Red baseline confirmed first: before the declaration, the same app built with `0 script phase(s)`, an empty sidecar, and no `EXConstants.bundle` at all. They also independently confirmed `deinit` leaves zero residue, `add` is idempotent (same sha1 twice), and no phase duplicates.
Their side is expo/expo#47647.
- **`end` appends at the true end of `buildPhases`**, which is *after* the JS bundle phase — where expo-constants must write, since it targets `$TARGET_BUILD_DIR`. Anchoring relative to the Frameworks phase (the obvious-looking choice) lands it *before* the bundle phase, because real template order is `Sources, Frameworks, Resources, Bundle React Native code and images`.
- **`beforeCompile` anchors after RN's own "Sync SPM Autolinking" phase**, which must stay first since it regenerates autolinking — a plugin phase ahead of it would run against stale generated content. The anchor chains forward so declared order survives. Position and relative order are re-derived every sync, so a phase dragged by hand in Xcode returns to its declared slot.
- **Validation is fatal**, matching `flavoredFrameworks` rather than the lenient `watchPaths`. A silently dropped phase means the content is never written and the app fails at runtime with no build-time signal — which is the bug being fixed.
- **`id` is the ledger key and the UUID seed.** The charset allows a scoped npm name (`expo/log-box`) but excludes `:`, which separates the `plugin:<id>` seed. `__proto__`/`constructor`/`prototype` are rejected because `plainObject['__proto__'] = v` vanishes through `JSON.stringify`, which would record a phase that `deinit` could never remove.
- **A plugin-supplied `name` reaches pbxproj comments**, and those are scanned by single-line regexes. What lands in a comment is therefore normalized: a name containing `*/`, `{` or `,` otherwise produced a brace-unbalanced project Xcode couldn't open, or an orphan phase `deinit` reported removing but didn't. The full name still goes verbatim into the `name` field Xcode displays. The same normalization now covers generated-source filenames, which had the identical hole.
`add → update → deinit` did **not** restore `project.pbxproj` byte-for-byte, even with zero script phases: the second run's marker forgot what the first had created, leaving an empty `packageReferences` / `packageProductDependencies` and the generated `.xcscheme` behind. The created-record now carries forward and `scheme.created` is sticky. Two guards came with that, both tested: a created array field is removed only when it is **empty** after RN's own members come out (so a package a user added to it survives `deinit`), and the scheme is deleted only if it is still RN's own (so a scheme the user has taken over is left alone).
[Internal] [Added] - SwiftPM: autolinking plugins can declare build-time script phases via `scriptPhases`
Pull Request resolved: https://github.com/react/react-native/pull/57757
Test Plan:
`yarn jest packages/react-native/scripts` → **853 tests**, all green. The SwiftPM suite specifically went from **462 → 637** tests.
Written red first throughout. Coverage includes:
- one declared phase → exactly one `PBXShellScriptBuildPhase`, correct `name`/`shellScript`/serialized paths; `alwaysOutOfDate` emitted as unquoted `1` only when set
- `end` lands last; `beforeCompile` lands after the sync phase and before Sources; declared order preserved for multiple phases of each position and for a mix; a changed `position` is re-seated on the next sync
- add / update-in-place / remove keyed on `id`; unchanged re-sync byte-identical; `deinit` byte-identical with no orphan object or section
- a 17-row hostile-`name` matrix (`{ } ( ) , ; = */ /* * /`, unbalanced quote, tab, unicode, 300 chars, a name that normalizes to empty) × {balanced after add, byte-identical re-inject, clean deinit}
- scripts containing quotes, backslashes, newlines and `$(VAR)` round-trip through emission, refresh and deinit
- contract validation: 16 malformed-entry cases, duplicate `id` across plugins, reserved ids, scoped ids accepted, `:` rejected
- `add → update → deinit` byte-identity with zero phases and with two
**Not covered by unit tests, deliberately:** that `end` runs after the JS bundle phase. The `plain-app.pbxproj` fixture has no bundle phase, so it is unassertable here — this is disclosed in a comment at the test rather than papered over, and is exactly what the Expo verification above establishes.
**Known limitation, not addressed here:** against a project Xcode has previously saved, `add → deinit → add` is structurally identical (same UUIDs, same reference counts) but not byte-identical — Xcode writes multi-line dicts in sorted order, the injector writes single-line dicts in insertion order, which shows up as formatting churn in a committed `project.pbxproj`. Pre-existing for every object the injector emits, not specific to script phases, and filed separately.
This touches `generate-spm-xcodeproj.js` and `spm-pbxproj.js`, which https://github.com/react/react-native/issues/57744 and https://github.com/react/react-native/issues/57756 also touch — including the same marker-write block. All three are cut independently from `main`; whichever lands first, I'll rebase the others. Happy to restack in whatever order is easiest to review.
Reviewed By: fabriziocucci
Differential Revision: D114318236
Pulled By: cipolleschi
fbshipit-source-id: 4aa7958c302323299eda9db17adcec0d86edeebe
(cherry picked from commit
|
||
|
|
8fafd46b69 |
fix(swiftpm): actually read the artifacts version pin back (#57762)
Summary:
**`spm add --version <ver>` pinned a value that nothing ever read back.**
The marker write has been there all along, and so has the reader — `readArtifactsVersionOverride()` in `spm/generate-spm-xcodeproj.js`, exported and unit-tested. It just had **zero production callers**. Every reference to it was a test.
`determineVersion` — the only resolver — went straight from the flag to `package.json`:
```js
let version = args.version; // --version
if (version == null) {
version = pkgJson.version; // react-native/package.json; marker never consulted
}
```
That value picks which artifact slots the project gets wired to. So:
1. `spm add --version 0.88.0-nightly-…` → wires the nightly's slots, pins the label ✅
2. `spm update` (no flag) → silently resolves `package.json`'s version instead, re-pointing the project at different slots, while the marker still claims the nightly ❌
`--version` was effectively single-use. In this monorepo `package.json` is `1000.0.0`, which has no published artifacts, so a flagless run after a pinned `add` fails outright — which is why the standing advice has been to pass `--version` on *every* invocation. That advice was working around this bug.
**Fix:** insert the pin between the two existing sources.
```
--version → pinned override → react-native/package.json
```
15 lines of logic. `spm download` and the scaffold path pick it up for free, since both consume the same resolved value. Because this is persistent state, one line is logged when the pin is the source, so a stale pin is diagnosable instead of silent; there is still no way to clear it short of `deinit`.
**Three comments were actively wrong** and are corrected here: the reader's doc block, the marker-field comment, and `findInjectedXcodeproj`'s comment all asserted that the build-time sync calls this via `readArtifactsVersionOverride`. It does not and never did — the `sync` action returns before artifacts are resolved at all. Those comments are what made the gap invisible; they misled me while investigating.
## Changelog:
[Internal] [Fixed] - SwiftPM: `spm --version` now sticks, so a later run without the flag keeps using the pinned artifact version
Pull Request resolved: https://github.com/react/react-native/pull/57762
Test Plan:
`yarn jest packages/react-native/scripts` → **31 suites, 684 tests**, all green.
New tests, written red first — the load-bearing one failed with `Expected: "0.80.0" / Received: "1000.0.0"`, i.e. exactly the reported bug:
- `--version` given → wins, even with a different value pinned
- no flag, pin present → the pinned version is used
- no flag, no pin → `package.json`'s version (unchanged behaviour)
- no flag, corrupt or absent marker → `package.json`'s version, no throw
- the log line fires only when the pin is the source
The three fallback cases passed *before* the fix too, which is the point — they pin today's behaviour so this change can't regress it.
## Note on landing order
Touches `setup-apple-spm.js` and `spm/generate-spm-xcodeproj.js`, which https://github.com/react/react-native/issues/57744, https://github.com/react/react-native/issues/57756 and https://github.com/react/react-native/issues/57757 also touch. All are cut independently from `main`; whichever lands first, I'll rebase the rest. https://github.com/react/react-native/issues/57756 is the closest relative — it does the same wiring for `--config-command`, which had the identical write-only-pin shape.
Reviewed By: fabriziocucci
Differential Revision: D114318107
Pulled By: cipolleschi
fbshipit-source-id: 157da5b2eaaef9adee924eaa1832b7a38b008f4c
(cherry picked from commit
|
||
|
|
f4ef5bdde6 |
Fix ConcurrentModificationException in IntentModule.getInitialURL re-entrancy (#57667)
Summary:
`IntentModule.onHostResume()` iterates `pendingOpenURLPromises` (an `ArrayList`) directly and calls `getInitialURL()` for each pending promise. When `getCurrentActivity()` returns `null` at that moment — e.g. a deep link or notification tap landing mid activity-transition during a rapid pause/resume — `getInitialURL()` re-enters `waitForActivityAndGetInitialURL()`, which calls `pendingOpenURLPromises.add(promise)` on the very list being iterated. The next iteration then throws `java.util.ConcurrentModificationException`:
```
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.util.ArrayList$Itr.next(ArrayList.java:967)
at com.facebook.react.modules.intent.IntentModule$waitForActivityAndGetInitialURL$1.onHostResume(IntentModule.kt:90)
```
The `synchronized(this@IntentModule)` guard does not prevent this: the re-entrancy is on the same thread, which already holds the (reentrant) lock, so no second lock acquisition happens. The crash is the `ArrayList` iterator's `modCount` check, not a cross-thread race.
The fix snapshots the pending promises into a local copy, clears the shared list, and nulls the listener **before** draining. Re-queued promises then land in the now-empty `pendingOpenURLPromises` and register a fresh listener for the next resume, instead of mutating the list being iterated. Behaviour is otherwise unchanged.
## Changelog:
[ANDROID] [FIXED] - Fix ConcurrentModificationException when getInitialURL re-enters during onHostResume
Pull Request resolved: https://github.com/react/react-native/pull/57667
Test Plan:
Added `IntentModuleTest.getInitialURL_onHostResumeWithNullActivity_doesNotThrowAndPreservesPromise`, a Robolectric regression test that registers a pending promise while the current activity is `null`, then drives `onHostResume` so the drain re-queues, and asserts the drain does not throw and the promise is preserved (neither resolved nor rejected).
Ran locally against `main` with the exact reproduction scenario:
**With the fix** — passes:
```
$ ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest \
--tests "com.facebook.react.modules.intent.IntentModuleTest"
BUILD SUCCESSFUL
# tests=1, failures=0, errors=0
```
**Without the fix** (reverting `IntentModule.kt` only) — fails with the exact crash, confirming the test is a genuine regression test:
```
IntentModuleTest > getInitialURL_onHostResumeWithNullActivity_doesNotThrowAndPreservesPromise FAILED
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.util.ArrayList$Itr.next(ArrayList.java:967)
at com.facebook.react.modules.intent.IntentModule$waitForActivityAndGetInitialURL$1.onHostResume(IntentModule.kt:90)
```
`ktfmt` (Meta style, matching the repo's `ktfmt` configuration) reports both changed files as already formatted.
Reviewed By: cortinico
Differential Revision: D113595327
Pulled By: fabriziocucci
fbshipit-source-id: b1247cb6e473fd7c550dda8b2b584d720ea3e46c
(cherry picked from commit
|
||
|
|
566f469009 |
Fix custom font weight rendering heaviest face on iOS Fabric (#57483)
Summary:
On the New Architecture (Fabric), a custom font referenced by its PostScript / full font name (e.g. `fontFamily: "Foo-Medium"`) combined with an explicit non-regular `fontWeight` renders at the heaviest available face (bold/black) instead of the requested weight. This is an iOS-only regression introduced in 0.83; 0.82 was correct. Android is unaffected.
**Root cause.** In `RCTFontUtils.mm`, `RCTFontWithFontProperties()` handles the case where the given `fontFamily` is actually a font name rather than a family name (`fontNames.count == 0`) and resolves the effective weight with:
```objc
fontWeight = (fontWeight != 0.0) ?: RCTGetFontWeight(font);
```
`fontWeight` is a `UIFontWeight` (a `double`: Regular = 0.0, Medium = 0.23, Bold = 0.4, Black = 0.62). The Objective-C "Elvis" operator `A ?: B` evaluates to **`A` itself** when `A` is truthy — and here `A` is the *comparison* `(fontWeight != 0.0)`, a `BOOL`. So whenever a weight was set (e.g. 0.23), `fontWeight` was reassigned to `1.0` (heavier than Black), and the subsequent "closest weight in the family" search always picked the heaviest face.
This was introduced by the automated implicit-bool-conversion sweep in https://github.com/react/react-native/issues/53591 (D81571883), which rewrote the original correct line `fontWeight ?: RCTGetFontWeight(font)` into `(fontWeight != 0.0) ?: …`. Making the truthiness check explicit is fine as a *condition*, but with `?:` the left-hand side is also the *returned value*, so the numeric weight got replaced by the boolean.
**Fix.**
```objc
fontWeight = (fontWeight != 0.0) ? fontWeight : RCTGetFontWeight(font);
```
For a `double`, `(A != 0.0) ? A : B` is exactly equivalent to the original `A ?: B`, so this restores the 0.81/0.82 behavior while keeping the explicit `!= 0.0` form used elsewhere in the file. The no-weight case is unchanged: `RCTResolveFontProperties` fills an unspecified weight with `UIFontWeightRegular` (0.0), so the weight is still inferred from the font name via `RCTGetFontWeight(font)` in that case. The legacy (Paper) path in `React/Views/RCTFont.mm` uses a different construct and is not affected — consistent with his only reproducing on the New Architecture.
## Changelog:
[iOS] [Fixed] - Custom fonts with an explicit fontWeight no longer render at the heaviest weight on the New Architecture
Pull Request resolved: https://github.com/react/react-native/pull/57483
Test Plan:
Fixes https://github.com/react/react-native/issues/54934.
New Architecture, iOS, with a custom font bundled and referenced by its PostScript name,
e.g. `<Text style={{ fontFamily: 'Foo-Medium', fontWeight: '500' }}>`:
| Version | Result |
| --- | --- |
| 0.82 | renders at the requested medium weight ✅ |
| 0.83 / `main` (before fix) | renders bold/black ❌ |
| removing `fontWeight` on 0.83 | renders correctly ✅ (confirms the weight branch is the culprit) |
After this change the text renders at the requested weight on both the old and new architecture.
Reviewed By: cortinico
Differential Revision: D111217210
Pulled By: javache
fbshipit-source-id: 76858e1b83f8f1032cb599aec6c4b0d49a6a7427
(cherry picked from commit
|
||
|
|
861e2d0bde | [LOCAL] Bump hermes-v1 to 250829098.0.17 | ||
|
|
363a116632 | [0.87] Use macOS 26 runners for iOS E2E tests (#58058) | ||
|
|
b8d50a9dff |
Fix Hermes bytecode version mismatch in SwiftPM Release builds (#57928)
Summary:
`react-native spm add`'s Release builds crash on launch with:
```
Compiling JS failed: Wrong bytecode version. Expected 99 but got 98
```
This happens because the SwiftPM integration resolves the Hermes **runtime**
(the downloaded `hermes-engine.xcframework`) and the Hermes **compiler** (the
`hermesc` binary that turns the JS bundle into bytecode) from two independent,
unsynced sources:
- `download-spm-artifacts.js`'s `resolveHermesArtifact()` picked the runtime
by querying the `hermes-compiler` package's `latest-v1` dist-tag on the npm
registry **live, at build time**.
- `generate-spm-xcodeproj.js`'s `resolveHermesCliPathSetting()` (and
`react-native-xcode.sh`, for the CocoaPods-free fallback) points
`HERMES_CLI_PATH` at the `hermes-compiler` package **already installed in
this project's own `node_modules`** — whatever got pinned the last time
`npm install` ran.
If the `latest-v1` dist-tag advances on npm between `npm install` and the
Release build (which happens routinely as new Hermes builds are published),
the downloaded VM and the locally pinned `hermesc` fall out of sync and the
app crashes at launch. `react-native-xcode.sh` already documents this exact
invariant ("react native pins the hermes-compiler version, so the compiler's
bytecode version always matches the prebuilt hermes VM artifacts") — SwiftPM's
artifact download just wasn't honoring it.
This PR makes `resolveHermesArtifact()` read the pinned `hermes-compiler`
version from `node_modules` first (the same `require.resolve` lookup already
used for `HERMES_CLI_PATH`), so the runtime download and the compiler always
agree. It falls back to the previous `latest-v1` npm lookup only when
`hermes-compiler` isn't locally resolvable (e.g. `USE_HERMES=false` apps that
never installed it). Explicit `HERMES_VERSION` overrides (`nightly`,
`latest-v1`, a literal version) are unchanged.
Fixes https://github.com/react/react-native/issues/57917.
## Changelog:
[IOS] [FIXED] - Fix Hermes runtime/compiler version mismatch causing "Wrong bytecode version" crashes in SwiftPM Release builds
Pull Request resolved: https://github.com/react/react-native/pull/57928
Test Plan:
Added unit tests covering the new local-resolution path, the fallback when
`hermes-compiler` isn't installed, and confirming existing `HERMES_VERSION`
overrides still take precedence over the local pin.
```
$ node_modules/.bin/jest packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js
Test Suites: 1 passed, 1 total
Tests: 70 passed, 70 total
$ node_modules/.bin/flow check packages/react-native/scripts/spm/download-spm-artifacts.js
No errors!
$ node_modules/.bin/eslint packages/react-native/scripts/spm/download-spm-artifacts.js packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js
(no output — clean)
$ node_modules/.bin/prettier --check packages/react-native/scripts/spm/download-spm-artifacts.js packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js
All matched files use Prettier code style!
```
Reproduced the crash and confirmed the fix end-to-end using the public
reproducer linked from the issue
(https://github.com/marandaneto/react-native-087-swiftpm-hermes-bytecode-repro):
- Before the fix: `npm run reproduce` builds successfully but launching the
app in the iOS Simulator crashes with `Compiling JS failed: Wrong bytecode
version. Expected 99 but got 98`.
- After applying the equivalent fix to the reproducer's installed
`react-native` copy: the log shows `Using locally pinned hermes-compiler:
250829098.0.16`, and both the debug and release Hermes runtime artifacts
resolve to that exact version — matching the `hermesc` used for
`HERMES_CLI_PATH`. `xcodebuild ... -configuration Release` succeeds, and
the app installs and launches cleanly on an iPhone 17 Pro (iOS 26.5)
simulator with no crash.
Reviewed By: cortinico
Differential Revision: D115859923
Pulled By: cipolleschi
fbshipit-source-id: 1b65a7aa28f502374a3553c1849b8ff29b5afd10
|
||
|
|
c6ebb060d9 | [LOCAL] Bump Podfile.lock | ||
|
|
4bc2473f5d |
Release 0.87.0
#publish-packages-to-npm&latestv0.87.0 |
||
|
|
a1942a74d6 | [LOCAL] Bump Podfile.lock | ||
|
|
01f752d73b |
Release 0.87.0-rc.4
#publish-packages-to-npm&nextv0.87.0-rc.4 |
||
|
|
24353daa87 |
fix(iOS): keep prebuilt Headers/ in place on a Debug/Release swap (#57814)
Summary:
Fixes https://github.com/react/react-native/issues/57803. An iOS Release build can fail in `PrecompileModule React` with seven `include of non-modular header inside framework module` errors — but only when the build follows a Debug/Release configuration switch.
`replace-rncore-version.js` deleted and recreated `Pods/React-Core-prebuilt/Headers/` on a swap. That directory holds `module.modulemap`, which `rncore.rb` activates on every target through `-fmodule-map-file`. Nothing orders an unrelated target's dependency scan against this script phase, so a scan can run while the module map is missing. The React module is then precompiled without it, and `<yoga/...>`, `<react/...>` and `<RCTDeprecation/...>` resolve non-modularly.
Those headers never needed replacing. The prebuild compose job emits one set of ReactNativeHeaders for both configurations, so they are identical in the Debug and Release tarballs — only the compiled framework differs. This replaces `React.xcframework` and nothing else.
## Changelog:
[IOS] [FIXED] - Keep the prebuilt `Headers/` in place on a Debug/Release configuration switch so the React explicit module still resolves its module map
Pull Request resolved: https://github.com/react/react-native/pull/57814
Test Plan:
The premise, on the published 0.87.0-rc.3 artifacts (`ios-arm64_x86_64-simulator`):
| compared between the Debug and Release tarballs | result |
| --- | --- |
| `ReactNativeHeaders…/Headers/module.modulemap` | identical |
| `React.framework/Modules/module.modulemap` | identical |
| `ReactNativeHeaders…/Headers` tree (`diff -rq`) | 0 differences |
| `React.framework/Headers` tree (`diff -rq`) | 0 differences |
The reproducer from https://github.com/react/react-native/issues/57803, on Xcode 26.3 with CocoaPods 1.15.2:
| build | result |
| --- | --- |
| 0.87.0-rc.3 | **FAIL** — exit 65, 7 errors |
| 0.87.0-rc.3 + this PR | **PASS** — `** BUILD SUCCEEDED **`, 0 errors |
The swap still does its job in the passing build — it logs `Replacing React-Core-prebuilt/React.xcframework`, and the installed binary is the Release one:
```
installed: 55225ccbc283c57c614ff4caf263cb63bad3828240e62cee8893e7001774bd6c
rc3 release: 55225ccbc283c57c614ff4caf263cb63bad3828240e62cee8893e7001774bd6c
rc3 debug: 516215801a6f8a86640aae13c2f2de1bbdb95189edf124e208f528b1497c7e4c
```
A Release→Debug swap was verified the same way. Across a swap, `Headers/module.modulemap` keeps its inode while `React.xcframework` gets a new one.
## Unit tests
Adds a unit test for the script, 4 cases: correct framework installed, `Headers/module.modulemap` untouched, an Expo-generated `React-use-frameworks.modulemap` left in place, and a fail-closed case on a tarball with no `React.xcframework`. The script needed a `require.main === module` guard and one export to be importable.
```
js1 test xplat/js/react-native-github/packages/react-native/scripts/__tests__/replace-rncore-version-test.js
→ 4 passed, 4 total
```
The module-map case is a real regression test, not just a pin. Restoring the pre-fix delete-and-recreate makes it fail on the inode assertion while the other three keep passing:
```
✕ leaves Headers/module.modulemap untouched
Expected: 735095485
Received: 735095515
```
That only works because the fixture tarball also ships `ReactNativeHeaders.xcframework`. Without it the pre-fix code throws its fail-closed error before reaching the assertion, so the test would go red for the wrong reason and would not actually be guarding #57803.
The Expo case covers behaviour this diff removes the explicit protection for. The old save-and-restore of `React-use-frameworks.modulemap` (
|
||
|
|
329f8640bf |
fix(swiftpm): persist --config-command so the in-build sync keeps using it (#57756)
Summary:
**`spm add --config-command` worked once, then broke every build.**
An app that replaces `react-native-community/cli` autolinking — an Expo app, for instance — has to override the autolinking config command, which `--config-command` (and `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`) exists to do. But the flag was never stored anywhere. So:
1. `npx react-native spm add --config-command '[...]'` → succeeds, writes a valid project ✅
2. Build in Xcode → the "Sync SPM Autolinking" phase re-derives `autolinking.json`, doesn't know about the flag, falls back to the default command, and fails ❌
```
PhaseScriptExecution failed with a nonzero exit code
→ Sync SPM Autolinking
→ 'npx --no-install react-native-community/cli config' exited with status 1
```
The only real workaround was exporting `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` into Xcode's environment — i.e. committing it to `.xcode.env` — which shouldn't be necessary when you already passed a flag. Reported by the Expo team while testing SwiftPM.
**Fix:** pin the command into the `.spm-injected.json` marker at `add`/`update` time, and read it back on later runs — the same set-or-preserve pin the neighbouring `artifactsVersionOverride` already uses.
Resolution order, unchanged at the front and only extended at the back:
```
--config-command → RCT_SPM_AUTOLINKING_CONFIG_COMMAND → pinned value → default
```
**Both input routes persist.** The help text advertises the env var as an equivalent way to supply the command, so pinning only the flag would have left half the documented interface broken in exactly the same way — export the env var, run `spm add`, and the build phase (which does not inherit your shell) still fails. The pin therefore stores the *resolved* command from either route.
Two subtleties worth a reviewer's eye:
- `generateAutolinkingConfig` resolves the env var *internally* when no explicit command is passed, so handing it the pin would silently outrank a developer's env override. The read path therefore **withholds** the pin while the env var is set, letting the existing precedence do its job. The four order cases are tested, including that a whitespace-only env var falls through to the pin rather than stranding it.
- A pinned value is re-validated through the same `parseConfigCommandJson` the flag goes through, so a hand-edited or corrupt marker degrades to the env/default command instead of injecting a bogus argv into a build.
Because this is now persistent state, `add`/`update` logs one line when the command comes from the pin, naming `.spm-injected.json` — a stale pin should be diagnosable from build output rather than invisible. There is no "clear" verb short of `deinit`, same as the version pin; that is noted in the marker comment.
122 added lines across three source files. No new mechanism, no changes to the sync scripts, and nothing baked into the generated build phase.
**Noticed while here, filed separately, deliberately not fixed:** `readArtifactsVersionOverride` — the version pin this is modelled on — has **no production caller**. Only its write half is wired, and two comments claim the build-time sync reads it. Those comments are corrected here (they misled me while writing this); wiring the version pin up is its own change.
### This isn't blocking anyone
Expo's SwiftPM verification is **not blocked** on this, so it needn't be rushed. The env-var half of the override already works at build time: adding
```sh
export RCT_SPM_AUTOLINKING_CONFIG_COMMAND='["node","…/expo-modules-autolinking.js","react-native-config","--json","--platform","ios"]'
```
to the app's `.xcode.env` (or `.xcode.env.local`) gets the command into the sync phase, because the generated phase sources both files before dispatching. That is what unblocks Expo today, and it is exactly the "commit an env var to `.xcode.env`" step this PR removes the need for.
One caveat that argues for fixing it properly rather than documenting the workaround: the phase sources `.xcode.env` **only when `NODE_BINARY` is unset** (`nodeAndRnDirPreamble`). An app that sets `NODE_BINARY` as an Xcode build setting — a documented RN practice — never sources those files, so the workaround silently does nothing there and the build fails with no hint as to why. Persisting the flag doesn't depend on any of that plumbing.
## Changelog:
[Internal] [Fixed] - SwiftPM: persist `spm --config-command` so the in-build autolinking sync keeps using it
Pull Request resolved: https://github.com/react/react-native/pull/57756
Test Plan:
`yarn jest packages/react-native/scripts` → **31 suites, 703 tests** (25 new).
Each new test written red first, covering:
- the command round-trips through `.spm-injected.json` (marker content asserted)
- `add` → `update` **without** the flag keeps the pin; a later flag overwrites it
- `add` with **only the env var** set pins the env-derived command, and a later run with neither flag nor env resolves it back
- all four resolution-order cases, including that **the env var beats the pin**, and that a whitespace-only env var pins nothing and falls through
- an invalid non-blank env var still fails loud rather than pinning garbage
- a corrupt or hand-edited pin (bare string, `[]`, non-string member, empty-string member, object) degrades to the default rather than throwing
- `deinit` drops it with the marker
Not covered: no test drives a real `sync` end to end, since that needs artifacts, codegen and a real pbxproj. The two halves are tested separately against the same marker field — the injector writes `configCommand`, and the resolver reads it. The original failure was reported from a real Expo app build; confirmation that this fixes that build is still pending on the Expo side.
Reviewed By: fabriziocucci
Differential Revision: D114317929
Pulled By: cipolleschi
fbshipit-source-id: a0fe47de3da55b25e320b000a2b0b1b44b88af9a
(cherry picked from commit
|
||
|
|
4dd4d31458 |
Write the prebuilt module-map flag to OTHER_CPLUSPLUSFLAGS too (#57742)
Summary:
`add_prebuilt_header_search_paths` (`scripts/cocoapods/rncore.rb`) injects the prebuilt ReactNativeHeaders module map into `OTHER_CFLAGS` and `OTHER_SWIFT_FLAGS`, but not `OTHER_CPLUSPLUSFLAGS`. C++ and ObjC++ translation units therefore lose modular resolution of the relocated `react/`, `yoga/` and `RCTDeprecation/` namespaces.
This is a regression: 0.86's VFS implementation (`add_vfs_overlay_flags`) wrote all three compiler-flag keys, the modular rewrite writes two. This adds the third back, reusing the existing quoted `module_map_flag`.
Xcode's `OTHER_CPLUSPLUSFLAGS = $(OTHER_CFLAGS)` default doesn't cover for it, because an explicit value *replaces* the C flags instead of merging — and React Native sets the key explicitly, both in `new_architecture.rb` and in the `pod_target_xcconfig` of ReactCodegen, React-RCTAppDelegate and React-RCTAnimatedModuleProvider.
To be clear about what this is: a correctness fix, not a bug fix. No build fails today, because `HEADER_SEARCH_PATHS` is injected unconditionally and the includes still resolve textually. What is lost is modular resolution.
## Changelog:
[IOS] [FIXED] - Write the prebuilt module-map flag to `OTHER_CPLUSPLUSFLAGS` so C++/ObjC++ sources resolve the relocated namespaces modularly
Pull Request resolved: https://github.com/react/react-native/pull/57742
Test Plan:
On an app using the prebuilt RNCore (`RCT_USE_PREBUILT_RNCORE=1`, artifacts from Maven), counting pods whose xcconfig carries the module-map flag per key:
| | `OTHER_CFLAGS` | `OTHER_CPLUSPLUSFLAGS` | `OTHER_SWIFT_FLAGS` |
|---|---|---|---|
| before | 211 | **0** | 211 |
| after | 211 | 211 | 211 |
`xcodebuild` succeeds after the change (`** BUILD SUCCEEDED **`, 0 errors), so the flag is accepted by `ScanDependencies` and the explicit-module precompile. This was a static-library build; framework linkage was not exercised.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed By: huntie
Differential Revision: D114044307
Pulled By: cipolleschi
fbshipit-source-id: 5c04f753d10876f36d31358593f1dfc29698b633
(cherry picked from commit
|
||
|
|
f3e11c2557 |
Fix compat with extensionless scripts/* imports (#57718)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57718
**Context**
Replaces https://github.com/react/react-native/pull/57709, which identified a real bug in 0.87 from the combination of:
- https://github.com/react/react-native/pull/57276
- https://github.com/react/react-native/pull/57652
```
$ npx react-native spm add
error Cannot find module '.../node_modules/react-native/scripts/setup-apple-spm'
$ npx react-native codegen
error Cannot find module '.../node_modules/react-native/scripts/codegen/generate-artifacts-executor'
```
**This diff**
- Fix — and exclusively switch to — extensionless imports rather than requiring `.js`.
- Update in-repo consumers.
The previous single mapping is now an extension-aware mapping:
- `./scripts/*` now appends `.js`, so `react-native/scripts/foo` resolves to `foo.js`.
- `.sh` and `.rb` stay reachable via explicit `./scripts/*.sh` and `./scripts/*.rb` passthrough patterns.
**Impact**
- Explicit `react-native/scripts/*.js` specifiers no longer resolve, so JavaScript paths must be imported without an extension.
- Files under `scripts/` with extensions other than `.js`, `.sh`, or `.rb` are no longer exposed through `./scripts/*`.
- These have no open source consumers.
Changelog:
[General][Fixed] - (RC4 only, drop for main changelog): `react-native/scripts/*` imports once again expand `.js` extensions
[General][Breaking] - Extensionless `react-native/scripts/*` imports are now **mandated**; explicit `.js` import specifiers are rejected.
Reviewed By: rubennorte
Differential Revision: D113898792
fbshipit-source-id: d72f60be2c08ab97871e336645856c9029e74ae2
(cherry picked from commit
|
||
|
|
8fffb64288 |
fix(rn-tester): use framework-style import for RCTFabricComponentsPlugins.h (#57697)
Summary:
rn-tester's `MyNativeView` example imports `RCTFabricComponentsPlugins.h`. The quoted form resolves under CocoaPods (the bare filename is re-vended via `FACADE_REEXPOSED_HEADERS` in `rncore_facades.rb`) and under the internal Meta build (the per-target header generated by buck's `plugins_header`), but not under SwiftPM. Under SwiftPM the header is exposed as an include directory, so only `<React/RCTFabricComponentsPlugins.h>` resolves. The quoted import makes `MyNativeView` fail to compile when rn-tester is converted to SwiftPM (surfaced by the new SPM CI lane in https://github.com/react/react-native/issues/57659), and switching to the angle form alone would instead break the internal build.
The fix uses a `__has_include` conditional: the angle form when it is available (SwiftPM and CocoaPods) with the quoted form as a fallback (the internal build). This matches the existing pattern in rn-tester's own `NativeCxxModuleExample.h` and keeps all three build stacks compiling.
## Changelog:
[INTERNAL] [FIXED] - Make rn-tester's NativeComponentExample header import resolve under SwiftPM, CocoaPods and the internal build
Pull Request resolved: https://github.com/react/react-native/pull/57697
Test Plan:
- Repro on clean `main`: converting rn-tester to SwiftPM (`spm add --deintegrate`) and building fails at `RNTMyNativeViewComponentView.mm` with `fatal error: 'RCTFabricComponentsPlugins.h' file not found`.
- With this change: the same SwiftPM conversion plus `xcodebuild -configuration Debug -sdk iphonesimulator` gives `BUILD SUCCEEDED`.
- Internal build: `buck2 build fbsource//xplat/js/react-native-github/packages/rn-tester:NativeComponentExampleApple` succeeds (it takes the quoted fallback).
- Existing CocoaPods CI (`test_ios_rntester`) covers the CocoaPods path.
Reviewed By: cipolleschi
Differential Revision: D113771766
Pulled By: fabriziocucci
fbshipit-source-id: e4549508f8e56942b45fa128b2391a290499d4a8
(cherry picked from commit
|
||
|
|
d8b52df809 |
SPM: allow overriding the autolinking config command (#57662)
Summary:
The SwiftPM autolinking flow hardcodes `react-native-community/cli config` to generate `autolinking.json` (`generate-spm-autolinking-config.js`). Apps that replace community autolinking — most notably **Expo**, which ships `expo-modules-autolinking` instead of `react-native-community/cli` — had no way to override that command, and any failure was swallowed. The result: the config command fails, `autolinking.json` is never written, the `Autolinked` SwiftPM package comes out empty, and `import Expo` (and every Expo module) fails to resolve — surfacing much later as an inscrutable `unable to resolve module dependency: 'Expo'`.
CocoaPods already solves the injection half: `use_native_modules!(config_command = $default_command)` accepts the command as a parameter, so an Expo `Podfile` passes `expo-modules-autolinking react-native-config` in place of the `rncli` default. This PR adds the equivalent hook to the SwiftPM path **and** closes the silent-failure trap.
### 1. Allow overriding the config command
`generateAutolinkingConfig` already accepted a `configCommand` option internally; it was just never reachable. Two ways to supply it, mirroring the CocoaPods hook:
- **`--config-command '<json>'`** — CLI flag taking a JSON array of the argv.
- **`RCT_SPM_AUTOLINKING_CONFIG_COMMAND`** — env var in the same JSON-array format. This is the vehicle for the injected Xcode build phase, which usually can't rewrite the script's argv but can read env.
Both go through one `parseConfigCommandJson` validator (rejects non-JSON, non-arrays, empty arrays, and non-string / empty-string elements, with a `source`-named error). Precedence: **`--config-command` > `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` > default** (local `rncli` → `npx --no-install` fallback, unchanged). JSON (rather than whitespace-splitting) because real commands contain dashed flags and quoted script strings.
The value is the **command to execute** — its stdout is captured as the config JSON and written verbatim — exactly matching CocoaPods, not a precomputed result. An Expo app feeds the same argv it already builds for `use_native_modules!`:
```jsonc
RCT_SPM_AUTOLINKING_CONFIG_COMMAND='["node","--no-warnings","--eval","require('expo/bin/autolinking')","expo-modules-autolinking","react-native-config","--json","--platform","ios","--source-dir","/abs/path"]'
```
(Use `--platform ios`: the generator requires `project.ios.sourceDir` and everything downstream is iOS-only.)
### 2. Fail closed when the config command errors
Previously `main()` swallowed a config-command failure as a warning and continued, which is what let the empty package be produced silently. That policy is now extracted into `generateAutolinkingConfigOrFailClosed`: on a config-command error (non-zero exit, unparseable output, or a config missing `project.ios.sourceDir`) it logs an actionable message naming `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` / `--config-command`, sets `process.exitCode = 2` (a hard Xcode build-phase error, matching the existing `RemoteVersionError` path), and stops.
The guard is deliberately narrow: a **genuinely native-module-free app never reaches the error path** — its command exits 0 with valid, empty-dependency JSON, so the generator returns normally and the legitimate empty-package path stays valid. Only an *erroring* command fails the build.
## Changelog:
[IOS] [ADDED] - Allow overriding the SwiftPM autolinking config command via `--config-command` / `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`
[IOS] [CHANGED] - Fail closed with an actionable error when the SwiftPM autolinking config command fails, instead of silently emitting an empty Autolinked package
Pull Request resolved: https://github.com/react/react-native/pull/57662
Test Plan:
New unit tests, developed red → green:
- `generate-spm-autolinking-config-test.js` — env var honored; explicit `configCommand` beats env; invalid-JSON and invalid-shape (`[]`, `[1,2]`) throw with the source name; env unset falls back to the default command; env state saved/restored per test.
- `setup-apple-spm-test.js` — `parseArgs` parses `--config-command` into an argv array, defaults to `null` when omitted, and throws on an invalid value; `generateAutolinkingConfigOrFailClosed` returns the result on success (exit code untouched), passes `projectRoot`/`configCommand` through, and on a config-command error returns `null`, sets exit 2, and logs an actionable error that names the env var and preserves the underlying cause.
```
$ yarn jest --no-cache -i \
packages/react-native/scripts/spm/__tests__/generate-spm-autolinking-config-test.js \
packages/react-native/scripts/spm/__tests__/setup-apple-spm-test.js
Test Suites: 2 passed, 2 total
Tests: 39 passed, 39 total
```
`prettier` and `eslint` clean on all changed files.
Reviewed By: zeyap
Differential Revision: D113554857
Pulled By: cipolleschi
fbshipit-source-id: d0baeeefc91aed144cf9e405e198531ff88088a7
(cherry picked from commit
|
||
|
|
0cacb90bc0 |
SPM: fix raw Flow type annotations in bare-node scripts (#57660)
Summary:
The scripts under `packages/react-native/scripts/spm/` are executed as plain `node` (via `setup-apple-spm.js` during the SwiftPM Xcode build), with **no Babel** to strip Flow. They use Flow-in-comment syntax (`/*: T */`) throughout so they parse un-transpiled.
A handful of uninitialized `let X: T;` declarations had slipped in with **raw** Flow annotations. Node parses the whole file on `require`, so each one throws `SyntaxError: Unexpected token ':'` at load time — taking the entire module down before it can run.
The jest suites didn't catch it because jest runs these files through `react-native/babel-preset`, which strips raw and comment-form annotations alike. Only the bare-`node` shipped path (SwiftPM setup) hits the error.
## Fix
Convert each offending declaration to Flow-comment form. Prettier's `flow` parser only keeps a comment type attached to the binding when the declaration is **initialized**, so each site gets a type-appropriate (inert) initializer — every variable is reassigned in the immediately-following `try`/branch before any use:
| file | lines | form |
|---|---|---|
| `autolinking-plugins.js` | 120, 178 | `/*: unknown */ = undefined` |
| `download-spm-artifacts.js` | 946, 973 | `/*: string */ = ''` |
| `download-spm-artifacts.js` | 1352 | `/*: {...} */ = {}` |
| `generate-spm-autolinking.js` | 429 | `/*: Array<...> */ = []` |
| `generate-spm-xcodeproj.js` | 1802 | `/*: string */ = ''` |
| `generate-spm-xcodeproj.js` | 1808 | `/*: unknown */ = undefined` |
| `generate-spm-xcodeproj.js` | 1863 | `/*: Array<...> */ = []` |
| `scaffold-package-swift.js` | 200, 1129 | `/*: Array<...> */ = []` |
| `scaffold-package-swift.js` | 933 | annotation dropped — Flow infers `PodspecModel` from `readPodspec()` |
## Changelog:
[INTERNAL] [FIXED] - Fix raw Flow type annotations that broke bare-node execution of the SwiftPM setup scripts
Pull Request resolved: https://github.com/react/react-native/pull/57660
Test Plan:
- `node --check` passes on all 14 `scripts/spm/*.js` (previously `autolinking-plugins.js` and `download-spm-artifacts.js` threw `SyntaxError` at parse time).
- `flow focus-check` reports **0 errors** in the touched files.
- Prettier: clean.
- SPM jest suites: green.
## Changelog:
[INTERNAL] [FIXED] - Fix raw Flow type annotations that broke bare-node execution of the SwiftPM setup scripts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed By: cortinico
Differential Revision: D113554595
Pulled By: cipolleschi
fbshipit-source-id: b7fa29dd54266b749e1671333fb0530f6b6a8aab
(cherry picked from commit
|
||
|
|
034245a015 |
fix(iOS): stage Hermes headers in prebuild compose job so ReactNativeHeaders resolves <hermes/...> (#57661)
Summary:
The iOS prebuild workflow (`.github/workflows/prebuild-ios-core.yml`) splits into two jobs on separate runners:
- **`build-rn-slice`** stages the hermes-ios headers during setup (`.build/artifacts/hermes/destroot/include/hermes`), but uploads only `.build/headers` + the SPM Products — **not** the hermes artifact.
- **`compose-xcframework`** (fresh runner) runs the compose (`-c` → `buildXCFrameworks`), which computed the hermes include path from `.build/artifacts/hermes/destroot/include`. That directory never exists on the compose runner, so `hermesHeaders` resolved to `null` and the hermes-header fold in `headers-compose.js` was **silently skipped**.
Net effect: the published `ReactNativeHeaders.xcframework` shipped without the `hermes/` namespace, so consumers (e.g. Expo prebuilt) couldn't resolve `<hermes/...>`. Because the value was `null` rather than a bad path, not even the existing warning fired — every nightly regressed silently.
Build-time staging, matching the artifact's self-contained design (the orphaned consumer-side sidecar + health check in `download-spm-artifacts.js` confirm the bake was intended to happen at compose time):
- **Workflow:** `compose-xcframework` now re-stages the hermes-ios headers before composing — a `Set Hermes version` step (`$GITHUB_ENV` doesn't cross jobs) and a `Stage Hermes headers` step that extracts the tarball into `.build/artifacts/hermes`. Both are guarded by the same `cache-hit` condition as the sibling steps.
- **Fail-closed guard:** the inline hermes resolution is extracted into an exported `resolveHermesHeaders(buildFolder, required)` with a `findFirst` fallback (mirroring the consumer-side stager). When a version-stamped CI cut can't find the headers it now **throws** instead of silently shipping without `hermes/`. Gated behind a new `--require-hermes` flag, which the workflow passes only when `version-type` is set — so local `-c` keeps the previous no-fold behavior.
[IOS] [FIXED] - Prebuilt `ReactNativeHeaders.xcframework` now ships the Hermes public headers so consumers resolve `<hermes/...>` out of the box
Pull Request resolved: https://github.com/react/react-native/pull/57661
Test Plan:
- New unit tests for `resolveHermesHeaders` (`__tests__/xcframework-test.js`): resolves at the standard path, resolves via the `findFirst` fallback, returns `null` when absent + not required, throws when absent + required.
- `yarn jest packages/react-native/scripts/ios-prebuild/__tests__/xcframework-test.js --no-cache -i` → 4/4 pass (red before the resolver was exported).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed By: javache
Differential Revision: D113554720
Pulled By: cipolleschi
fbshipit-source-id: 30d21240ae97978e58a95ffdfad4088ed85536d4
(cherry picked from commit
|
||
|
|
ed8973a04e | [LOCAL] Bump Podfile.lock | ||
|
|
b67c5cf8b4 |
Release 0.87.0-rc.3
#publish-packages-to-npm&nextv0.87.0-rc.3 |
||
|
|
9a7b821a02 |
Fix TS exactOptionalPropertyTypes compatibility for generated types (#57628) (#57699)
Summary: Pull Request resolved: https://github.com/react/react-native/pull/57628 NOTE: Patches over a `flow-api-translator` bug, which I'll fix upstream later. We need to pick this to `0.87-stable` to resolve user integration issues. **Context** TypeScript's `exactOptionalPropertyTypes` flag (strict mode) creates a distinction between `foo?: T` and `foo?: T | undefined`. ```js // Flow's semantics interface Props { onRefresh?: () => void; } const a: Props = { onRefresh: undefined }; // ✅ ok ``` ```ts // TypeScript with exactOptionalPropertyTypes: true (i.e. strict mode) interface Props { onRefresh?: () => void; } const a: Props = { onRefresh: undefined }; // ❌ error interface PropsFixed { onRefresh?: (() => void) | undefined; } const b: PropsFixed = { onRefresh: undefined }; // ✅ ok ``` With this added strictness in TypeScript, our generated types via `flow-api-translator` could create downstream type incompatibility in apps. **This diff** Patches the above issue in React Native's Flow → TS `types_generated/` pipeline. We transform all instances to the wider `foo?: T | undefined` format, for maximum compatibility. **Notes** `foo?: T [| undefined]` **remains stripped** in the API snapshot (existing transform with the aim of a concise format). There is a net, nonfunctional snapshot diff around function members, which (as a positive result) are re-ordered. Changelog: [General][Fixed] - **Strict TypeScript API**: Optional property types are now widened to explicitly include `| undefined` for `exactOptionalPropertyTypes` compatibility Reviewed By: cipolleschi Differential Revision: D113030161 fbshipit-source-id: 3ab005edab6b80b18fbb9ae7125ba56e3bd94195 Co-authored-by: Alex Hunt <huntie@meta.com> |
||
|
|
330080c46b |
fix(iOS): make jsinspector-modern tracing state types move-only for Swift C++ interop (#57605)
Summary: The nightly-tests job `[ios] react-native-unistyles` fails on Xcode 26.3 with: ``` error: no matching function for call to '__construct_at' note: in instantiation of member function 'std::vector<...RuntimeSamplingProfile>::vector' requested here note: in implicit copy constructor for 'facebook::react::jsinspector_modern::tracing::TraceRecordingState' first required here ``` while compiling the **Swift** files of the Unistyles pod. The same failure hits any library built with Swift C++ interop (`-cxx-interoperability-mode=default`) — in practice, every Nitro-based library — against the prebuilt React Native core. ### The error `TraceRecordingState` and `HostTracingProfile` hold `std::vector`s of move-only types (`RuntimeSamplingProfile` and `FrameTimingSequence` explicitly delete their copy constructors). Here's the C++ subtlety: `std::vector<T>`'s copy constructor is **declared for every `T`** — it only becomes ill-formed when *instantiated*. So the implicit copy constructors of these two structs are not implicitly deleted; they exist as declared-but-broken constructors that hard-error the moment anything asks for a copy. ### Why React Native compiles fine today Nothing in RN ever asks. Every usage passes these types by reference; the single constructions move. A pure C++ (or ObjC++) build never instantiates the implicit copy constructors, so this code has always compiled — and always would, no matter how much C++ CI you throw at it. The defect is unobservable from within C++. ### What fails, and why now The prebuilt-core headers now ship as real clang modules. A Swift target with C++ interop imports them (directly or transitively — e.g. via a module member whose `#ifdef __cplusplus` body opens because interop builds modules with C++ enabled), and Swift's ClangImporter surfaces the C++ value types to Swift as copyable. When the consumer's generated interop code then uses such a type as a Swift value — for a Nitro-based library, the nitrogen-generated `*_cxx.swift` bridging does exactly this — the compiler **synthesizes a copy of the type, instantiating the ill-formed implicit copy constructor**. That is the "ask" that plain C++ never makes; on Xcode 26.3 it hard-errors the entire module import, killing every Swift file in the consumer. (Newer Swift toolchains treat such types as non-copyable instead of failing.) Before the prebuilt-modules work there was no Swift-visible module containing these headers, so no interop consumer ever imported these types — which is why this surfaces now despite the C++ being unchanged. We deliberately did **not** fix this by removing headers from the module maps: the guarded-C++-in-modules pattern is shared by ~30 legitimately modular headers and is benign in all but this one shape, and experiments showed the type is reachable through multiple independent module surfaces (removing one member just moved the error to the next path). ### The fix Declare the truth: make both types explicitly move-only. ```cpp TraceRecordingState(const TraceRecordingState &) = delete; TraceRecordingState &operator=(const TraceRecordingState &) = delete; TraceRecordingState(TraceRecordingState &&) = default; TraceRecordingState &operator=(TraceRecordingState &&) = default; ``` With the copy constructor explicitly deleted, Swift's importer sees a non-copyable type and imports it as such instead of instantiating a broken copy. It is also simply more correct C++: these types were never copyable in practice, and the explicit deletion turns any future accidental copy into a clear compile error at the call site instead of a template backtrace. Declaring special members makes `HostTracingProfile` a non-aggregate, so its one designated-initializer construction site (`HostTargetTraceRecording.cpp`) is converted to member-wise assignment. A sweep of the affected header surface (`std::vector`/`std::map`/`std::deque` of move-only element types) found exactly these two types; a follow-up adds a `headers-verify.js` gate that imports the shipped modules under Swift C++ interop at prebuild time, so the next type with this shape fails RN's own CI instead of community nightlies. ## Changelog: [IOS] [FIXED] - Fix Swift C++-interop build failure (implicit copy constructor of TraceRecordingState/HostTracingProfile) for libraries using cxx interop with prebuilt React Native core Pull Request resolved: https://github.com/react/react-native/pull/57605 Test Plan: On a fresh RN-nightly app with stock `react-native-unistyles@3.3.0` + `react-native-nitro-modules`, prebuilt core (`RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1`), Xcode 26.3: - **Red**: stock headers reproduce the CI failure exactly (`__construct_at` → `TraceRecordingState`). Fixing only `TraceRecordingState` then surfaces the identical failure on `HostTracingProfile` — confirming the shape, not the type, is the bug. - **Green**: with both headers fixed (stock module maps, nothing else changed): BUILD SUCCEEDED — zero `__construct_at`, zero `shadowNodeFromValue`, zero module errors. - All RN-internal usages audited: references and moves only; no behavior change. Plain C++/ObjC++ compilation unaffected by construction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D112802806 Pulled By: cipolleschi fbshipit-source-id: d2ae201c1fbb4de040f51ebc88263c7347b5979b |
||
|
|
514f104ca6 |
fix(iOS): stamp the React Native version in the prebuild compose job (#57603)
Summary: `prebuild-ios-core.yml` builds the prebuilt iOS core in two jobs: **`build-rn-slice`** compiles the React binary per platform slice, and **`compose-xcframework`** runs afterwards in its **own fresh checkout**, downloads the slice artifacts, and composes the shipped `React.xcframework` + `ReactNativeHeaders.xcframework` — re-deriving the shipped *headers* from that checkout. The "Set React Native version" step runs only in `build-rn-slice`. So the compiled **binary is stamped**, but the composed **headers are not**: the artifact ships `ReactNativeVersion.h` with the `1000.0.0` dev sentinel, internally inconsistent with its own binary (and with the npm package, which is stamped in its own publish job). This was latent for as long as the compose job has existed — it only started breaking consumers now because the header-facades / VFS-overlay-removal work changed *which file libraries actually read*. Previously header resolution (the VFS overlay, and the `Pods/Headers/Public` symlinks of the source pods) redirected reads to the **stamped npm copy**, so the sentinel bytes in the tarball were never consumed. With real materialized headers, the prebuilt copy now wins the search path, and any library gating on `REACT_NATIVE_VERSION_MAJOR/MINOR` compiles the wrong branch. That breaks `[ios] react-native-unistyles` in nightly-tests: its `#if REACT_NATIVE_VERSION_MINOR >= 81` sees `MINOR 0` and picks a removed pre-0.81 path (`shadowNodeFromValue`). **Fix — built-headers overlay (no re-stamp/revert).** The `build-rn-slice` job already stamps its checkout before building and uploads `.build/headers`, and the compose job already downloads it — it was just unused. `stageEntries` now takes an overlay dir and, **for `ReactNativeVersion.h` only**, prefers the built copy (stamped in the slice job) over the source sentinel. Header **layout** is still spec-derived from source (podspec inventory, collision detection, classification), and every **other** header still copies from source — so a stale build tree can never ship wrong header content, only the one build-generated version header is overlaid. No stamping of the compose checkout, no `git revert`, no cache-key desync. The compose cache key is bumped so pre-fix (unstamped) composed artifacts cached under the old key are not served. **Guard.** `headers-verify.js` gains `--require-stamped-version` (passed when a `version-type` is set) that hard-fails the compose verification if any composed `ReactNativeVersion.h` still contains the sentinel — turning any future regression into a red prebuild instead of silently broken community libraries. ## Changelog: [IOS] [FIXED] - Ship a version-stamped ReactNativeVersion.h in the prebuilt iOS core artifacts instead of the 1000.0.0 dev sentinel Pull Request resolved: https://github.com/react/react-native/pull/57603 Test Plan: - Local compose with a stamped `ReactNativeVersion.h` seeded into `.build/headers` and the source tree left at the `1000` sentinel: all composed `ReactNativeVersion.h` copies come out stamped (overlay won), while a deliberately **stale** `.build/headers/…/TraceRecordingState.h` is correctly ignored — the composed `TraceRecordingState.h`/`HostTracingProfile.h` come from source, and other headers + module maps/umbrellas are unaffected (structural gate passes). - Guard: composed layout with a sentinel header → `verifyVersionStamp` throws listing the offending files; stamped → `version stamp OK (N copies checked)`; no effect without the flag. - Verified against a fresh RN-nightly app + `react-native-unistyles@3.3.0` on Xcode 26.3 (prebuilt mode) and in the Expo prebuilt harness (26.3 + 26.6): stock prebuilt headers reproduce the `shadowNodeFromValue` error; the stamped prebuilt header resolves it. - End-to-end proof is the next nightly run: the composed tarball must carry a stamped header. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: javache Differential Revision: D113005220 Pulled By: cipolleschi fbshipit-source-id: 5fbba8bb4e84fd1458a2f4a0baa623cf613b6e3b |
||
|
|
50bad79b42 |
Fix post-release workflow failures (#57627)
Summary: Fixes the three post-release failures from the [0.87.0-rc.2 publish run](https://github.com/react/react-native/actions/runs/29775529785/): - Keep Flow annotations in `verifyArtifactsAreOnMaven.js` as comments so `actions/github-script` can load the file with plain Node. - Pin the Podfile lock workflow to `macos-15`, which provides the configured Xcode 16.4 version. - Use the canonical `react/react-native` owner for release asset API operations so upload POST requests are not redirected from the former owner. ## Changelog: [INTERNAL] [FIXED] - Fix post-release Maven verification, Podfile lock, and release asset jobs Pull Request resolved: https://github.com/react/react-native/pull/57627 Test Plan: ```sh node --check .github/workflow-scripts/verifyArtifactsAreOnMaven.js node --check scripts/releases/upload-release-assets-for-dotslash.js yarn jest .github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js scripts/releases/__tests__/upload-release-assets-for-dotslash-test.js --runInBand ``` 13 tests and 15 snapshots pass. Reviewed By: zeyap Differential Revision: D113032768 Pulled By: cipolleschi fbshipit-source-id: df5f493603e6c25157fd7660b54aa409dcb742e7 |
||
|
|
65c816879c |
Fix parentNode for nested root host views like <Modal> (#57623)
Summary: Pull Request resolved: https://github.com/react/react-native/pull/57623 `NativeDOM::getParentNode` returned the document for any shadow node carrying the `RootNodeKind` trait. That trait is set not only on a surface's actual root node but also on nested host views such as `<Modal>` (and portal/overlay-style components), so their `parentNode`/`parentElement` incorrectly resolved to the document instead of their real containing element. Because the EventTarget-based event dispatch builds its capture/bubble ancestor path by walking `getParentNode` up the shadow tree, this truncated the path at the modal boundary: a listener registered on an ancestor rendered above the modal never received events (e.g. focus/blur) originating inside it. Fix by returning the document only for the surface's actual root node, identified via `ShadowNode::sameFamily(*currentRevision, *shadowNode)`. Every other node — including nested root-kind nodes — now reports its real structural parent, while disconnected nodes still report none. Changelog: [General][Fixed] - Fix `parentNode`/`parentElement` returning the document instead of the containing element for `<Modal>` and other nested root host views, which severed capture/bubble event propagation to ancestors rendered above them Reviewed By: javache Differential Revision: D113012362 fbshipit-source-id: 88043a9b9de128e7c3c96d8516b0950cbd6c3c68 |
||
|
|
651bbdffc4 | [0.87] Update RNTester Podfile.lock for 0.87.0-rc.2 (#57624) | ||
|
|
9518500909 |
Release 0.87.0-rc.2
#publish-packages-to-npm&nextv0.87.0-rc.2 |
||
|
|
17bf68a4c3 |
[LOCAL] Set .hermesv1version to hermes-v250829098.0.16 (#57620)
Summary: The Android Gradle build downloads the Hermes source from https://github.com/facebook/hermes/tarball/<ref>, where <ref> is the contents of sdks/.hermesv1version. Release tags on facebook/hermes are named hermes-v<version>, so the bare value 250829098.0.16 404s and breaks the build_fantom_runner / downloadHermes build. Pin to the hermes-v prefixed tag. Test Plan: curl -sL -o /dev/null -w '%{http_code}' \ https://github.com/facebook/hermes/tarball/hermes-v250829098.0.16 => 200 |
||
|
|
d045236281 | Bump hermes version (#57616) | ||
|
|
88f43bc69a | [LOCAL] Fix CI: build rntester dynamic-frameworks lane from source (#57615) |