Christian Falch
90d · built 2026-08-09
90-day totals
- Commits
- 22
- Grow
- 14.2
- Maintenance
- 1.2
- Fixes
- 2.3
- Total ETV
- 17.7
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
↑+1800.0 %
vs 1 prior
↑+80.4 pp
recent vs prior
↑+12.7 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
| Repo | Commits | ETV |
|---|---|---|
| react-native | 21 | 17.7 |
Most impactful commits
Top 20 by ETV in the 90-day window.
- 11.4ETVfeat(iOS) swiftpm support in iOS (#57332) Summary: ⚠️ This is a stacked PR — review/merge the base PRs first Builds on, and should land after (bottom-up): 1. https://github.com/react/react-native/issues/57285 — Remove the Clang VFS overlay / modularize React headers (base of the stack). <React/…> via the framework module map; lowercase namespaces (react/, yoga/, jsi/, …) via the ReactNativeHeaders module map. 2. https://github.com/react/react-native/issues/57305 — Prebuilt artifact resources + ReactNativeHeaders.xcframework. Ships the prebuilt core artifacts (incl. ReactNativeHeaders in the tarball + embedded React.framework resources) and the CocoaPods React-Core-prebuilt facades. 3. https://github.com/react/react-native/issues/57440 — Self-serving prebuilt ReactNativeDependencies + dependency-only facades. The deps pod becomes the single header authority for the third-party namespaces (RCT-Folly/glog/boost/…); community `s.dependency "RCT-Folly"` resolves to local facades instead of trunk source pods. 4. https://github.com/react/react-native/issues/57442 — ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders (immediate base). The deps prebuild emits a headers-only LIBRARY-type sidecar (the binary deps xcframework is framework-type — invisible to SwiftPM binaryTargets), ReactNativeHeaders drops the third-party namespaces, and both headers artifacts publish standalone to Maven. The whole stack is rebased onto current main (2026-07-06). This PR consumes the final **five-artifact set**: React, ReactNativeHeaders (pure-RN), ReactNativeDependencies, ReactNativeDependenciesHeaders, hermes-engine. Adds npx react-native spm and the package-generation tooling that turns an app into a SwiftPM-integrated RN app using the base stack's prebuilt XCFrameworks. CocoaPods stays supported — additive, opt-in, no Ruby toolchain. Integration is injected into the existing .xcodeproj in place (nothing generated/renamed/replaced), recorded in .spm-injected.json so it reverses exactly. ### What this PR adds - The spm CLI (add/update/deinit/scaffold + hidden sync/codegen/download), zero-arg auto-resolution, --deintegrate for CocoaPods→SwiftPM. - SwiftPM package generation (scripts/spm/): autolinking→Package.swift, Codegen→React-GeneratedCode, core XCFramework binary targets, artifact download/cache, surgical pbxproj inject/remove. - ReactNativeDependenciesHeaders wired as the 5th binaryTarget: the headers-only companions (ReactNativeHeaders from the core tarball, the deps sidecar from the deps tarball) are staged automatically out of their parent tarballs — no --headers-tarball priming; REQUIRED_ARTIFACTS covers all five. - Community-library scaffolding from podspecs (incl. root-level-source podspecs via generated include/<SwiftName>/ shims, sibling wiring for transitive spm.dependencies, self-ingestion guards). - In-place Xcode integration (XCLocalSwiftPackageReference + Sync build phase + scheme pre-action + auto-sync; ${PODS_ROOT}-anchored REACT_NATIVE_PATH replaced on --deintegrate with exact deinit rollback). - rn-tester + helloworld SwiftPM consumption + test library; npm-packaging hygiene; a small RNCoreFacades.podspec_dir fix. ## Documentation - **Tool docs** (usage, quick start, architecture): [`scripts/spm/__doc__/spm-scripts.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-scripts.md) - **Header-paths contract** (how the five artifacts serve headers): [`scripts/spm/__doc__/spm-header-paths-contract.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-header-paths-contract.md) - **RFC** (updated): [react-native-community/discussions-and-proposals#994](https://github.com/react-native-community/discussions-and-proposals/pull/994) ## Changelog: [IOS] [ADDED] - `npx react-native spm` command + SwiftPM package-generation tooling (opt-in; CocoaPods stays supported) Pull Request resolved: https://github.com/react/react-native/pull/57332 Test Plan: 350 scripts/spm unit tests (incl. byte-identical add→deinit round-trip); manual E2E against the five-artifact set: fresh app via `cli init`→`spm add --deintegrate`→build (artifact resources verified in the app), rn-tester in-place migration (RNTesterPods.xcodeproj, test libraries + spmModules), helloworld in-place migration — all BUILD SUCCEEDED. Verified `npx react-native spm <cmd>` from the command line in each journey. ## Scope & limitations iOS, prebuilt-only (no build-from-source yet); full spm.xcframework/spm.source library metadata not yet (app-local spm.modules + scaffolding are); Expo not yet. ## Follow-ups Remote-mode ReactNative Package.swift must vend the ReactNativeDependenciesHeaders product (spm-distribution repo); library self-containment for repo-portable manifests; build-from-source. Reviewed By: mdvacca Differential Revision: D111449548 Pulled By: cipolleschi fbshipit-source-id: 87a873bf5c8be2d60f7893611e16924bfa730a46github.com-facebook-react-native · 47fad096 · 2026-07-15
- 1.9ETVrefactor(ios): remove clang VFS overlay, resolve headers via new ReactNativeHeaders framework (#57285) Summary: The prebuilt `React.xcframework` previously relied on a Clang VFS overlay (`React-VFS.yaml`) to make headers importable, because the headers were laid out in CocoaPods-style namespaced folders rather than standard framework conventions. The overlay had to be generated at build time, re-resolved at pod-install time per slice, and injected as `-ivfsoverlay` flags into every Obj-C, C++, and Swift compile (including aggregate and third-party pod targets). This is fragile, hard to reason about, and incompatible with SwiftPM consumption. This PR removes the VFS overlay entirely and resolves headers through standard framework/header-search-path mechanics instead. **Headers are now emitted into the artifact according to an explicit, executable spec:** - **`React.xcframework`** — each slice's `React.framework` carries every `<React/...>` header plus a framework module map, so `#import <React/...>` and `import React` resolve through `FRAMEWORK_SEARCH_PATHS` automatically. - **`ReactNativeHeaders.xcframework`** (new, headers-only) — carries every other namespace (`<react/...>`, `<yoga/...>`, `folly`, `glog`, …), shipped alongside in the prebuilt tarball and exposed via a single header search path. - This makes `ReactNativeDependencies` binary-only. No clang VFS overlay, no per-target `-ivfsoverlay` flags. The layout is driven by a single source of truth (`headers-spec.js`, rules R1–R11) that both the prebuild compose step and downstream SwiftPM tooling derive from, so the shipped header set cannot drift from the spec. Source headers are byte-identical to the repo — the only consumer-facing change needed is bare-form angle includes (`#import <RCTAppDelegate.h>` → `#import <React/RCTAppDelegate.h>`). **Consumer surfaces the flattened layout initially dropped are restored** (validated against Expo and community Fabric modules): - Private headers (`RCTBridge+Private.h` + the Fabric `RCTComponentView*` family) are exposed in the `React` module map — modular where safe, `textual` where they reach C++ — so frameworks like Expo compile unchanged, incl. Swift access to `RCTBridge.moduleRegistry`. - `React_RCTAppDelegate-umbrella.h` is re-emitted (derived from the live header set) for consumers probing it via `__has_include`. - Sources shipping under multiple include spellings (`React/X.h` + legacy `CoreModules/…`, `RCTImage/…`, bare aliases — 116 today) keep content at ONE module-owned spelling; other spellings become generated redirect shims, so `-fmodules` consumers cannot hit duplicate declarations. - The `React-RCTFabric` facade re-vends `RCTFabricComponentsPlugins.h` at `header_dir "React"`, keeping community Fabric modules' quoted `#import "RCTFabricComponentsPlugins.h"` working as with source pods. **The layout is verified at generator time** (`headers-verify.js`, runs in the prebuild compose CI job): unresolvable includes ratchet against a committed baseline, composed module maps/umbrellas must byte-match the spec render, and consumer-shaped compile smokes must pass (the `React` module, every namespace module, an Expo-shaped ObjC++ fixture, and a Swift `moduleRegistry` fixture). Fail-closed guards cover header collisions, allowlist drift, and missing OR undeclared third-party deps namespaces (the latter surfaced `SocketRocket`, which is deliberately NOT relocated — the real pod vends it, and textual copies collide under `use_frameworks`; the gate asserts its absence). **Key changes** - **New**: `headers-spec.js` (the executable layout contract, R1–R11), `headers-compose.js` (emitter for both xcframeworks), `headers-inventory.js` (podspec-driven header classifier feeding the spec + a diagnostic manifest), `headers-verify.js` (generator-time gate + CI step), `__docs__/headers-rules.md` (rules + rationale). - **Removed**: `vfs.js`, VFS types in `types.js`, and the VFS processing/flag-injection paths in `rncore.rb` and `xcframework.js`. - **Updated**: `React-Core-prebuilt.podspec` (vends both xcframeworks, flattens `ReactNativeHeaders` headers into `Headers/` via `prepare_command`, fails closed on incomplete tarballs), `rncore.rb` / `react_native_pods.rb` (header search path instead of overlay flags), `prebuild-ios-core.yml` (core tarball ships both xcframeworks; compose job verifies the composed headers), README (VFS docs replaced with the new model). - Added facades to the Podspecs that shouldn't be in use when running using precompiled frameworks to satisfy dependencies as empty pod-specs (with the single `RCTFabricComponentsPlugins.h` re-vend exception noted above). ## Changelog: [IOS] [CHANGED] - Remove the Clang VFS overlay from prebuilt React Native Core; resolve headers via React.xcframework + a new headers-only ReactNativeHeaders.xcframework Pull Request resolved: https://github.com/react/react-native/pull/57285 Test Plan: - [x] rn-tester builds against the prebuilt `React-Core-prebuilt` pod (Debug + Release) with no `-ivfsoverlay` flags present in the generated xcconfigs. - [x] rn-tester builds against React native source code (without any prebuilt artifacts) - [x] `#import <React/...>`, `import React;`, and the relocated namespaces (`<react/...>`, `<yoga/...>`, `folly`/`glog`) all resolve. - [x] Prebuilt tarball contains both `React.xcframework` and `ReactNativeHeaders.xcframework`; pod install flattens headers into `React-Core-prebuilt/Headers`. - [x] Switch RN-tester between Debug/Release and verify that both `React.xcframework` and `ReactNativeHeaders.xcframework` are changed between debug and release correctly. - [x] `headers-verify.js` gate green on Debug and Release composes (`-Werror=non-modular-include-in-framework-module` never trips in consumer builds). - [x] `private/helloworld` builds against the prebuilt core (CocoaPods path). - [x] Expo SDK compiles against the prebuilt artifacts (private headers, `React_RCTAppDelegate` umbrella probe, Fabric quoted imports). Reviewed By: fabriziocucci Differential Revision: D111448598 Pulled By: cipolleschi fbshipit-source-id: 72bc2f37765ad425722161a038e410ba976858a5github.com-facebook-react-native · 376bd0e4 · 2026-07-15
- 0.7ETVfeat(iOS): ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders, published to Maven (#57442) Summary: Step 2 of the prebuilt-deps roadmap: ship the deps headers as a **SwiftPM-ready, self-contained artifact** and make every header namespace have exactly **one physical home**. 1. **New artifact: `ReactNativeDependenciesHeaders.xcframework`** — the binary `ReactNativeDependencies.xcframework` is framework-type, so its root `Headers/` is invisible to SwiftPM binaryTargets (`HeadersPath` is rejected on framework entries; verified empirically). The deps prebuild now emits a headers-only library-type sidecar (stub archives + per-slice `Headers/` + `HeadersPath` — the exact `ReactNativeHeaders` recipe, factored into a shared `headers-xcframework.js` emitter) carrying all seven deps namespaces incl. SocketRocket, with slice parity derived from the binary artifact's Info.plist. Ships inside the deps tarball *and* standalone. 2. **`ReactNativeHeaders` goes pure-RN** — the R2 relocation of deps namespaces (and the `DEPS_NAMESPACES_NOT_RELOCATED` SocketRocket exclusion list) is deleted. Relocated copies are what enabled the SocketRocket dual-copy regression (duplicate `interface` / poisoned module graph under `use_frameworks!`); that bug class is now structurally impossible. Headers gate flipped: deps namespaces must be **absent** from RNH; the sidecar emitter enforces set-equality with `DEPS_NAMESPACES` fail-closed in both directions. On the CocoaPods side, a new `ReactNativeDependenciesUtils.configure_aggregate_xcconfig` injects the deps pod's `Headers/` globally (aggregate + every pod target), mirroring the rncore injection — this replaces the folly/glog resolution pods previously got via the flattened `React-Core-prebuilt/Headers`. 3. **CI: prebuilt + dynamic-frameworks lane** — the regression's exact config had no coverage (the `test-ios-rntester` action hard-coupled `use-frameworks:true` to source builds). New `use-prebuilds` input; `test_ios_rntester`'s dynamic cells now consume the workflow-built prebuilt artifacts. 4. **Maven publishing** — `ReactNativeHeaders` and `ReactNativeDependenciesHeaders` publish standalone on `react-native-artifacts` (classifiers `reactnative-headers-*`, `reactnative-dependencies-headers-*`); `verifyArtifactsAreOnMaven` now HEAD-checks every classifier tarball instead of only the POM. Stacked on https://github.com/react/react-native/issues/57440. The SwiftPM preview (https://github.com/react/react-native/issues/57332) rebases on top and wires the sidecar as its 5th binaryTarget. ## Changelog: [IOS] [CHANGED] - Prebuilt artifacts: ReactNativeHeaders is pure-RN; third-party deps headers ship in the new ReactNativeDependenciesHeaders.xcframework sidecar (and the ReactNativeDependencies pod), published standalone to Maven Pull Request resolved: https://github.com/react/react-native/pull/57442 Test Plan: - Headers gate: include-health, structural (deps absent from RNH, byte-matched module maps), and compile smokes (React module + 14 namespace modules + Expo-shape ObjC++/Swift fixtures vs the deps include path) — ALL PASSED - jest: 33/33 (`scripts/ios-prebuild/__tests__`, incl. new sidecar set-equality tests) - ESLint (`--max-warnings 0`), Prettier, Flow (`yarn flow-check`): clean - E2E (locally built artifacts): rn-tester prebuilt static ✅, prebuilt `USE_FRAMEWORKS=dynamic` ✅ (the regression config — verified `React-Core-prebuilt/Headers` contains no deps namespaces and the deps pod serves all seven), helloworld static ✅, source-core + prebuilt-deps ✅ (React compiled from source resolves folly via the deps pod), source-mode control with unchanged dependency graph ✅ - Sidecar inspected: per-slice `HeadersPath`, 7 namespaces, slice parity with the binary - Publication validated end-to-end with `publishReleasePublicationToMavenLocal`: all 12 files + POM land with the expected classifier names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D111449462 Pulled By: cipolleschi fbshipit-source-id: e1217d14c0588d00a207622d346c9e6f4705a95dgithub.com-facebook-react-native · 6aa147f6 · 2026-07-15
- 0.6ETVfix(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: a0fe47de3da55b25e320b000a2b0b1b44b88af9agithub.com-facebook-react-native · ed3229aa · 2026-07-31
- 0.6ETVrefactor(iOS): include resources like bundles into the precompiled XCFrameworks (#57305) Summary: **Depends on https://github.com/react/react-native/issues/57285** In source builds, `React-Core` ships non-header resources — its privacy manifest (`PrivacyInfo.xcprivacy`) and its localized strings (RCTI18nStrings) — via the podspec's resource_bundles. In the prebuilt path those source pods aren't installed (CocoaPods facades) or aren't present at all (SwiftPM), so these resources were silently dropped: prebuilt/SwiftPM apps shipped no React Native privacy manifest, and localized strings were unavailable. This embeds them in React.framework at prebuild time so they ship uniformly across CocoaPods-prebuilt and SwiftPM, with source builds unchanged: - **Privacy manifest** — the PrivacyInfo.xcprivacy of the pods baked into React.framework are merged into one manifest at the framework root. React.framework is a dynamic framework, so Xcode's privacy-report aggregation picks it up automatically (no runtime involvement). - **RCTI18nStrings** — rebuilt as RCTI18nStrings.bundle inside React.framework. RCTLocalizedString.mm now resolves the bundle from its own framework first (bundleForClass:), falling back to the app's main bundle for static source builds. It also fixes a latent bug in `React-Core.podspec`: a later resource_bundles = was overwriting the earlier resource_bundle =, so source builds had stopped shipping RCTI18nStrings. Both bundles are now declared together. With the artifact owning these resources, the prebuilt RNCore facade no longer carries them. ## Changelog: [IOS][FIXED] - Ship React-Core's privacy manifest and localized strings (RCTI18nStrings) inside the prebuilt React.xcframework, so CocoaPods-prebuilt and SwiftPM apps include them Pull Request resolved: https://github.com/react/react-native/pull/57305 Test Plan: - ✅ yarn jest packages/react-native/scripts/ios-prebuild — unit + integration tests for the merge/discovery/bundle-build logic (red/green). - ✅ Built React.xcframework from this branch and confirmed each slice's React.framework carries the merged PrivacyInfo.xcprivacy and RCTI18nStrings.bundle (37 locales). - ✅ Cold-built rn-tester in prebuilt mode and verified the app bundle contains Frameworks/React.framework/{PrivacyInfo.xcprivacy, RCTI18nStrings.bundle}. - ✅ Confirmed React-Core.podspec now reports both resource bundles (RCTI18nStrings, React-Core_privacy) for source builds. Reviewed By: fabriziocucci Differential Revision: D111448862 Pulled By: cipolleschi fbshipit-source-id: a95360f51cc1131510ef03f7e48c689575d1c22dgithub.com-facebook-react-native · 77b75122 · 2026-07-15
- 0.4ETVfeat(iOS): serve third-party deps from the prebuilt ReactNativeDependencies pod via dependency-only facades (#57440) Summary: Step 1 of making the prebuilt `ReactNativeDependencies` pod the **single header authority** for the third-party C/C++ deps (RCT-Folly, glog, boost, DoubleConversion, fmt, fast_float, SocketRocket) in prebuilt-deps mode. Today the deps **binary** replaces the source pods' code, but the pod still `s.dependency`'s the real source pods and borrows their headers via `$(PODS_ROOT)/<pod>` search paths. That split header authority is the dual-copy bug class behind the 2026-07-03 SocketRocket regression (`duplicate interface` under `use_frameworks!` — SocketRocket's ObjC headers have no include guards). Three commits: 1. **fix(cocoapods): harden prebuilt-deps header search paths and artifact handling** — `rndependencies.rb`'s `||= [] << path` only added the deps header search path when `HEADER_SEARCH_PATHS` was unset (silently dropped otherwise); normalize and always append, and point at the pod-local flattened `Headers/`. `ReactNativeDependencies.podspec` `prepare_command` now fails closed (`exit 1`) instead of silently producing a no-link pod. `reactNativeDependencies.js` no longer deletes + re-downloads a locally staged artifact that lacks a version marker. 2. **feat(cocoapods): dependency-only facades for third-party pods in prebuilt-deps mode** — in prebuilt-deps mode the real source pods are not declared, so a community podspec's hardcoded `s.dependency "RCT-Folly"` would resolve from trunk and compile from source next to the prebuilt binary. `RNDepsFacades` generates dependency-only local facade podspecs (no sources, no headers, a single dependency on `ReactNativeDependencies`); versions/subspecs are derived from the real podspecs in `third-party-podspecs/` (SocketRocket synthesized fail-closed from `socket_rocket_config`). Full contract documented in `scripts/cocoapods/__docs__/prebuilt-deps.md`. 3. **feat(ios-prebuild): SocketRocket privacy manifest + Xcode 26 header layout** — embed an RN-authored, accurate-empty `PrivacyInfo.xcprivacy` for SocketRocket (upstream ships none), and stage flat public headers into `include/` so Xcode 26's SwiftPM accepts the header layout. Stacked on https://github.com/react/react-native/issues/57305 (base: `chrfalch/prebuilt-resources`); the SwiftPM preview (https://github.com/react/react-native/issues/57332) rebases on top of this. Follow-up (separate PR): headers-only `ReactNativeDependenciesHeaders.xcframework` sidecar so SPM auto-serves the deps namespaces and `ReactNativeHeaders` goes pure-RN. ## Changelog: [IOS][CHANGED] - Prebuilt-deps mode: serve third-party headers from the ReactNativeDependencies pod itself and resolve community `s.dependency` on RCT-Folly/glog/boost/etc. via dependency-only facade pods Pull Request resolved: https://github.com/react/react-native/pull/57440 Test Plan: E2E matrix (2026-07-06, locally built deps artifact via `prepare-ios-prebuilds.js`): - rn-tester, prebuilt core + prebuilt deps, static linkage — builds - rn-tester, prebuilt core + prebuilt deps, `USE_FRAMEWORKS=dynamic` — builds (the SocketRocket-regression config) - helloworld (private), prebuilt core + prebuilt deps, static — builds - source-mode control: no facades generated, `Podfile.lock` identical to baseline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D111449257 Pulled By: cipolleschi fbshipit-source-id: ace5716868d126a08721200efd640e903b191658github.com-facebook-react-native · a8156acf · 2026-07-15
- 0.4ETVSPM: 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: d0baeeefc91aed144cf9e405e198531ff88088a7github.com-facebook-react-native · a7ba4ce5 · 2026-07-27
- 0.2ETVfix(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. ## Fix 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. ## Changelog: [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: 30d21240ae97978e58a95ffdfad4088ed85536d4github.com-facebook-react-native · 43b44ed7 · 2026-07-27
- 0.2ETVfix(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: 5fbba8bb4e84fd1458a2f4a0baa623cf613b6e3bgithub.com-facebook-react-native · 95df5003 · 2026-07-21
- 0.2ETVfix(iOS): gate prebuilt headers on Swift C++-interop importability (#57606) Summary: Adds a generator-time gate so a Swift-C++-interop-hostile C++ type can never silently ship in the prebuilt iOS core headers again. **The class this guards:** the prebuilt core headers ship as real clang modules. A C++ value type reachable from a shipped module whose *implicit copy constructor is declared but ill-formed on instantiation* — e.g. an implicit-copy struct with a `std::vector<move-only-T>` member — compiles fine in C++ but hard-errors when a Swift target with `-cxx-interoperability-mode=default` imports the module and its generated bridging uses the type as a copyable value. This is invisible to C++ CI (plain C++ never instantiates the copy), so it only surfaced as a broken community-library nightly (react-native-unistyles, via `TraceRecordingState` / `HostTracingProfile` — fixed in https://github.com/react/react-native/issues/57605). **The gate:** a fourth compile gate (stage 3d) in `headers-verify.js`'s `runCompileGates`, compiling a Swift TU with `-cxx-interoperability-mode=default` that imports every module declared by the composed `ReactNativeHeaders` module map (parsed at runtime, not hardcoded), plus a probe that forces the ClangImporter copyability path for a small explicit list of value types (`SWIFT_CXX_INTEROP_PROBE_TYPES`). Because a bare `import` does not eagerly instantiate copy constructors on the current toolchain, the probe reproduces what a real interop consumer's generated code does: for each listed type it conditionally instantiates a copy, which fails exactly as the real consumer fails if the type regresses to an implicit copy. Adding future coverage is a one-line list edit. One module (`React_RCTAppDelegate`) is excluded as non-importable under interop by design (ObjC bootstrap module with C++-only factory conformances); the inspector C++ graph it would reach is covered directly by the probe instead. The exclusion is documented in-source, and new exclusions are required to carry a justification. Independent review confirmed the probe reproduces the real failure for the right reason (the dangerous shape reports `is_copy_constructible_v == true`, so the probe enters the branch and forces the ill-formed body instantiation — mirroring ClangImporter, not masking the bug), and that both offender types are covered. ⚠️ **Land after https://github.com/react/react-native/issues/57605** — the gate runs against the composed artifacts, which are built from source; until the move-only type fixes are in, the gate correctly fails the prebuild on the unfixed types. ## Changelog: [INTERNAL] [ADDED] - Prebuild gate that fails composed iOS headers unusable from a Swift C++-interop consumer Pull Request resolved: https://github.com/react/react-native/pull/57606 Test Plan: Against a freshly composed Debug artifact (`node scripts/ios-prebuild -c -f Debug`): - **Green**: `node scripts/ios-prebuild/headers-verify.js --flavor Debug` passes the new gate with both probe types (`TraceRecordingState`, `HostTracingProfile`) fixed in the artifact. - **Red, TraceRecordingState**: restoring the unfixed `TraceRecordingState.h` into the composed artifact fails the gate with the exact `__construct_at` / implicit-copy diagnostic; restoring the fix → green. - **Red, HostTracingProfile** (proves coverage isn't accidental): same protocol with `HostTracingProfile` unfixed (TraceRecordingState left fixed) → gate fails naming `HostTracingProfile` and its probe specialization; restore → green. - `node --check`, `prettier --check`, `flow focus-check` clean. Gate is skipped by `--skip-compile` like the other compile gates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cortinico Differential Revision: D113011048 Pulled By: cipolleschi fbshipit-source-id: 51cfd9fde67c60bce9d13e490fe966ed059432c2github.com-facebook-react-native · 25ee70a1 · 2026-07-27
- 0.2ETVfix(iOS): dedupe SPM package module maps by building spm_dependency pods into the shared products dir (#57602) Summary: The nightly-tests job `[ios] react-native-enriched-markdown` fails on Xcode 26.3 (the CI runner version) with `error: redefinition of module 'RaTeXFFI'`. The library declares an `spm_dependency` on a Swift package (RaTeX) containing a C-family target, and Xcode emits the package's generated `include/module.modulemap` **twice**: into the shared products dir and into the pod's own `CONFIGURATION_BUILD_DIR`. Both copies are unavoidably visible to the pod's compile: - the package's `.swiftmodule` **serializes** the products-root include path in its recorded clang args and replays it on import, and - Xcode's built-in `-I$(BUILT_PRODUCTS_DIR)/include` exposes the per-pod copy. Clang parses two files defining the same module and hard-errors on Xcode 26.3 (26.5+ tolerates the duplicate). Because the root path is serialized inside the swiftmodule, no search-path change can hide it — and the `SWIFT_ENABLE_EXPLICIT_MODULES=NO` opt-out (https://github.com/react/react-native/issues/57589, and the equivalent podspec-side opt-out already shipped in react-native-enriched-markdown) is confirmed insufficient: the failing nightly contains it. **Fix**: build static-library `spm_dependency` pod targets straight into the shared products dir (`CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}`), so both module-map references resolve to the *same file* (clang dedupes same-path module maps). This also places the package swiftmodule on the pod's default search path, which replaces the old `SWIFT_INCLUDE_PATHS` "Swift package not found" workaround for these targets. The aggregate xcconfigs' `-fmodule-map-file` references are rewritten to the flattened location by mutating the shared in-memory `Config` objects (later `post_install` steps re-save those same objects, so a plain file edit would be clobbered). `USE_FRAMEWORKS=dynamic` keeps the previous behavior unchanged. Supersedes https://github.com/react/react-native/issues/57589. ## Changelog: [IOS] [FIXED] - Fix "redefinition of module" build failure on Xcode 26.3 for pods using spm_dependency with prebuilt React Native core Pull Request resolved: https://github.com/react/react-native/pull/57602 Test Plan: All on a fresh RN-nightly app with `react-native-enriched-markdown` under `RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1` (the nightly-tests configuration): - Red baseline reproduced CI exactly on Xcode 26.3 (13× `redefinition of module 'RaTeXFFI'`). - With this change (plain `pod install`, clean DerivedData): **BUILD SUCCEEDED on Xcode 26.3, 26.6, and 27.0 beta 3**, Debug and Release, zero module errors. - `USE_FRAMEWORKS=dynamic`: unchanged behavior (legacy code path taken, verified via pod install log). - Regression net without any `spm_dependency` pod (change must be inert): helloworld × {prebuilt, source} × {static, dynamic} × Debug — 4/4 build + install + launch PASS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cortinico Differential Revision: D112796678 Pulled By: cipolleschi fbshipit-source-id: f6e988fae8954b3b245180e6a66d600a6406a75egithub.com-facebook-react-native · 4a662070 · 2026-07-20
- 0.2ETVfix(iOS): exclude C++-guarded foreign-namespace headers from React umbrella (#57575) Summary: The first nightly containing the prebuilt/SwiftPM stack (`0.88.0-nightly-20260716`) broke the `react-native-unistyles` job in [react-native-community/nightly-tests](https://github.com/react-native-community/nightly-tests/actions/runs/29476835418/job/87551592185) with: ``` Pods/Headers/Public/React-Core-prebuilt/react/timing/primitives.h:28:7: error: redefinition of 'HighResDuration' note: additional include site in header from module 'ReactNativeHeaders_react' note: additional include site in header from module 'React.RCTFrameTimingsObserver' → could not build module 'React' ``` **Root cause:** `RCTFrameTimingsObserver.h` includes `<jsinspector-modern/tracing/FrameTimingSequence.h>` under `#ifdef __cplusplus`, transitively reaching `react/timing/primitives.h`, which is owned by the `ReactNativeHeaders_react` module. The headers inventory's reachability fixpoint only follows *unguarded* include edges (modeling a pure-ObjC consumer), so it couldn't see this edge and classified the header as umbrella-safe. Swift pods with C++ interop (unistyles, Nitro-based libs) build the `React` clang module as ObjC++, opening the guard — one header owned by two modules in a single compilation → C++ redefinition errors. Pure-ObjC consumers never open the guard, which is why only C++-interop libraries failed. **Fix:** a curated `UMBRELLA_CXX_GUARDED_EXCLUSIONS` set checked in `isUmbrellaSafe`, mirroring the existing `PRIVATE_REACT_HEADERS` pattern, with fail-closed validation (`validateUmbrellaExclusions`, wired into `planFromInventory`) so a renamed/deleted entry surfaces immediately. The header still ships in `React.framework/Headers` and stays importable via `#import <React/RCTFrameTimingsObserver.h>` (its only consumer today is `RCTHost.mm`); it is only removed from the modular umbrella surface. If a modular consumer ever needs it, it can be promoted to a `PRIVATE_REACT_HEADERS.textual`-style entry instead. ## Changelog: [IOS] [FIXED] - Fix "redefinition of 'HighResDuration'" / "could not build module 'React'" when building Swift pods with C++ interop against the prebuilt React-Core artifact Pull Request resolved: https://github.com/react/react-native/pull/57575 Test Plan: - New unit tests in `scripts/ios-prebuild/__tests__/headers-spec-test.js`: the excluded header is kept out of the umbrella (fails against the previous code with `Received array: ["React/RCTFrameTimingsObserver.h"]`), and exclusion drift fails closed. - Full ios-prebuild suite: 5 suites, 58/58 tests passing; prettier/eslint clean. - E2E: to be confirmed via a nightly/prebuilt cut building react-native-unistyles in prebuilt mode (`RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: sammy-SC Differential Revision: D112320697 Pulled By: cipolleschi fbshipit-source-id: 9c7d1d2cb035ac821992914d8df4db7245905834github.com-facebook-react-native · 9847238e · 2026-07-16
- 0.2ETVfix(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` (ada39220a0) is unnecessary now that only `React.xcframework` is replaced, but nothing else pins it. ## Argument parsing `yargs` parsing moved inside the `require.main === module` guard, so importing the module no longer parses `process.argv`. Verified in both directions. The command line still performs the swap end to end: ``` $ node replace-rncore-version.js -c Release -r <version> -p <podsRoot> Replacing React-Core-prebuilt/React.xcframework Updating React-Core-prebuilt/.last_build_configuration with Release Done replacing React Native prebuilt installed binary: binary-Release module.modulemap inode before=735141703 after=735141703 last_build marker: Release ``` Importing with hostile argv (`-c` collides with jest's `--config`) has no side effects: ``` $ node -e "process.argv = ['node','jest','-c','jest.config.js','--version']; require('./replace-rncore-version.js')" imported OK, exports: replaceRNCoreConfiguration ``` `arc lint` is clean on both files. Reviewed By: zeyap Differential Revision: D114735639 Pulled By: fabriziocucci fbshipit-source-id: 35ead7dae9ce7ad7160005a15ecb3975817ae728github.com-facebook-react-native · df5e6f6a · 2026-08-04
- 0.1ETVfix(iOS): prevent UUID collision corrupting Pods project in spm_dependency (#57576) Summary: The first nightly containing the prebuilt/SwiftPM stack (`0.88.0-nightly-20260716`) broke the `react-native-enriched-markdown` job in [react-native-community/nightly-tests](https://github.com/react-native-community/nightly-tests/actions/runs/29476835418/job/87551590732): `pod install` succeeds, but xcodebuild cannot load the generated project: ``` -[XCRemoteSwiftPackageReference _setSavedArchiveVersion:]: unrecognized selector sent to instance Failed to load container at path: .../Pods/Pods.xcodeproj — "The project 'Pods' is damaged and cannot be opened." ``` **Root cause:** `spm_dependency` injects `XCRemoteSwiftPackageReference` / `XCSwiftPackageProductDependency` objects from a `post_install` hook via `project.new(...)`. CocoaPods' `Pod::Project` overrides UUID generation with a deterministic counter scheme (`prefix + %07X counter + trailing 0`) and deliberately skips collision checks, assuming the project is always freshly generated. When the generator counters are out of sync with the loaded object graph, the first generated UUID collides with an existing object — in the observed failure the root `PBXProject`'s own UUID (`46EB2E00000000`). The injected package reference silently overwrites the root object entry in `objects_by_uuid`, so the saved pbxproj's `rootObject` points at the package reference, and Xcode's loader sends `PBXProject`-only messages to it. The bug is latent in `spm_dependency` since its introduction; the recently grown Pods object graph merely exposed it. **Fix:** route object creation through a `new_object` helper that keeps the deterministic counter scheme but probes `generate_uuid` forward past any UUID already present in `objects_by_uuid`. Uniqueness is guaranteed against the live object table regardless of project size/state; `generate_uuid`'s counter is strictly monotonic, so the probe terminates and never repeats within a run. Repeated `pod install`s stay idempotent via the existing find-before-create dedup guards. ## Changelog: [IOS] [FIXED] - Fix "The project 'Pods' is damaged and cannot be opened" when a library uses `spm_dependency` and the generated UUID collides with an existing Pods project object Pull Request resolved: https://github.com/react/react-native/pull/57576 Test Plan: - New standalone test `scripts/cocoapods/__tests__/spm-test.rb` (runs via `ruby scripts/cocoapods/__tests__/spm-test.rb`, uses the real `Pod::Project`): - fresh-project injection (passes before and after — fresh counters don't collide, isolating the trigger), - injection after counter reset (reproduces the corruption against the previous code as `NoMethodError: undefined method 'product_ref_group' for ... XCRemoteSwiftPackageReference` on reload — the Ruby analog of Xcode's crash; passes with the fix), - global UUID uniqueness + clean `Xcodeproj::Project.open` round-trip. - 3 tests, 10 assertions, 0 failures with the fix. - E2E: to be confirmed with a `spm_dependency` library (e.g. react-native-enriched-markdown) in prebuilt mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D112320831 Pulled By: cipolleschi fbshipit-source-id: de190f6492abff2cc6dbd94347e5346d11b50a41github.com-facebook-react-native · 1cdf784a · 2026-07-16
- 0.1ETVfix(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: d2ae201c1fbb4de040f51ebc88263c7347b5979bgithub.com-facebook-react-native · 38611186 · 2026-07-21
- 0.1ETVCI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app) (#57659) Summary: Adds CI coverage for the SwiftPM (SPM) iOS path, which currently has none. Three standalone workflows scaffold + convert an app to SPM using the prebuilt XCFrameworks and compile it for the iOS simulator: - `test-ios-spm-rntester.yml` — `packages/rn-tester` - `test-ios-spm-helloworld.yml` — `private/helloworld` - `test-ios-spm-newapp.yml` — a fresh copy of `private/helloworld`, wired to a Verdaccio-published build of this monorepo None of them use `react-native-community/cli`. The shared logic lives in two committed, locally-runnable scripts (`scripts/e2e/spm-prime-artifacts.js`, `scripts/e2e/spm-ios-e2e.js`); the workflows are thin wrappers. They run manually (`workflow_dispatch`) and nightly (`schedule`). This also adds an optional `slices` input to `prebuild-ios-dependencies.yml` and `prebuild-ios-core.yml`. It defaults to the full platform set, so existing callers (`test-all.yml`, releases) are unchanged; the e2e workflows request only `ios-simulator`, since that is all they compile against. ## Changelog: [INTERNAL] [ADDED] - CI workflows validating the SwiftPM iOS build (RNTester, HelloWorld, new app) Pull Request resolved: https://github.com/react/react-native/pull/57659 Test Plan: - Trialed the HelloWorld workflow on this branch (temporary push trigger, since removed): full run green for both Debug and Release, incl. the real `spm add` + `xcodebuild`. The `slices` input correctly collapsed the prebuild matrices to the single `ios-simulator` slice per flavor. - `node --check`, `prettier --check`, and `eslint` clean on both scripts; all workflow YAML validated. Reviewed By: huntie Differential Revision: D114044420 Pulled By: cipolleschi fbshipit-source-id: cb8723ae6d78bb765f3dc930d8010d4eb82ea4c0github.com-facebook-react-native · 1208178d · 2026-07-31
- 0.1ETVSPM: 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: b7fa29dd54266b749e1671333fb0530f6b6a8aabgithub.com-facebook-react-native · e9eac585 · 2026-07-27
- 0.1ETVfix(cocoapods): escape local file URIs for unicode paths (#56907) Summary: https://github.com/facebook/react-native/issues/56878 shows that using unicode characters in paths fails with pod install. Expo fixed this internally here: https://github.com/expo/expo/pull/45779 Fixes https://github.com/facebook/react-native/issues/56878 ## How: Build local `file://` sources through a shared helper that percent-encodes filesystem paths before passing them to `URI::File.build`. This avoids Ruby/CocoaPods URI failures when React Native projects or local prebuilt tarballs live under paths containing Unicode characters or spaces. Apply the helper to `RNCore` and `ReactNativeDependencies` local tarball sources, including RNCore dSYM-processed prebuilt paths, while leaving remote Maven and Sonatype URLs unchanged. Add focused coverage for Unicode, spaces, ASCII paths, and the raw `URI::File.build` regression case. ## Changelog: [IOS] [FIXED] - fixed escaping local file URIs for unicode paths in ruby scripts Pull Request resolved: https://github.com/facebook/react-native/pull/56907 Test Plan: - Test with creating a new project in a path containing a unicode character and the run pod install. Reviewed By: cortinico Differential Revision: D105968728 Pulled By: cipolleschi fbshipit-source-id: 6d5b9043aafe3d86494f3b5ca0f208e46f1f3739github.com-facebook-react-native · 9563cb5b · 2026-05-22
- 0.0ETVfix(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: e4549508f8e56942b45fa128b2391a290499d4a8github.com-facebook-react-native · 6d0612c2 · 2026-07-27
- 0.0ETVfix(cocoapods) Podfile.lock SPEC CHECKSUMS drift for React XCFrameworks (#56803) Summary: Two developers (or one developer on two paths, or CI vs. local) running pod install on the same React Native project at the same commit get different SPEC CHECKSUMS entries for React-Core-prebuilt and ReactNativeDependencies in Podfile.lock. That breaks pod install-deployment style verification and any workflow that expects Podfile.lock to be reproducible. CocoaPods derives each SPEC CHECKSUMS entry by hashing the in-memory podspec JSON. So anything embedded in source.http, prepare_command, user_target_xcconfig, etc. becomes part of the hash. Two podspec-resolution sites in this repo build their source.http from an absolute on-disk path. Because project_pods_root is an absolute path, the resulting file://<abs>/... URL differs across machines or working-tree paths, so the hashed JSON differs, so the checksum differs. ### How The Maven URL for each tarball is already computed inside both functions (stable_tarball_url(...) / nightly_tarball_url(...) / release_tarball_url(...)). Returning that URL — a stable string identical across machines — instead of the local file:// URL makes source.http path-free. CocoaPods downloads from Maven and caches the tarball itself, so functionality is preserved. The pre-existing local-tarball download is kept untouched (its only remaining consumer is the opt-in `RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS=1` dSYM-injection path in rncore.rb, which still needs file:// to feed CocoaPods a mutated tarball — gated by unless @download_dsyms so the leak is preserved only for that flag). **Out of scope: ** hermes-engine.podspec has the same shape of leak in user_target_xcconfig.HERMES_CLI_PATH. Fixing it requires a paired change to ensure hermesc lands at the new ${PODS_ROOT}-relative path; that's a separate PR. ## Changelog: [IOS] [FIXED] - Fix Pod install checksum drifting Pull Request resolved: https://github.com/facebook/react-native/pull/56803 Test Plan: Run `pod install` and verify that none of the following files contains absolute paths: - Pods/Local Podspecs/React-Core-prebuilt.podspec.json - Pods/Local Podspecs/ReactNativeDependencies.podspec.json Reviewed By: christophpurrer Differential Revision: D104889112 Pulled By: CalixTang fbshipit-source-id: 97505a8bf78f7df57bda2d87705da5a20c934d2bgithub.com-facebook-react-native · 814ebb12 · 2026-05-13