Christian Falch
90d · built 2026-09-08
Performance
What Christian Falch shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
+5.9engineers
delivers like 6.9 (6.9x pre-AI)
Output (ETV)
17.7ETV
+4962.9% vs 0.4 prior
Features share
40.7%
+40.7 pp vs prior window
Fixes share
10.3%
−81.1 pp vs prior window
Work mix
40.7% Features4.3% Maintenance36.4% Tests8.4% Docs10.3% Fixes
32 commits over 90 days, ending 2026-09-08.
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
| Repo | Commits | ETV |
|---|---|---|
| react-native | 31 | 17.7 |
Most impactful commits
Top 10 by ETV in the last 90 days.
- 8.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.6ETVfeat(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. ### Verified end to end by the Expo team 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. ### Design decisions worth a reviewer's attention - **`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. ### Also fixed in passing `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). ## Changelog: [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. ## Note on landing order 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: 4aa7958c302323299eda9db17adcec0d86edeebegithub.com-facebook-react-native · 6ff47ef9 · 2026-08-10
- 1.4ETVrefactor(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
- 1.2ETVResolve 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). ## Changelog: [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: 23269f4d959b84ac484be1911e8a863c74008ff9github.com-facebook-react-native · 962aeec5 · 2026-08-25
- 0.6ETVfix(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: 08bc81f80855721bf2123143063462109a99ac25github.com-facebook-react-native · 8ac3e2fa · 2026-08-18
- 0.6ETVfeat(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.4ETVrefactor(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.4ETVfix(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.3ETVSPM: 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.3ETVDerive 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: 2882e512489cc4784a09aeb03769e97d64d286f7github.com-facebook-react-native · d36aa496 · 2026-08-21