Skip to content

refactor(iOS): include resources like bundles into the precompiled XCFrameworks#57305

Open
chrfalch wants to merge 28 commits into
mainfrom
chrfalch/prebuilt-resources
Open

refactor(iOS): include resources like bundles into the precompiled XCFrameworks#57305
chrfalch wants to merge 28 commits into
mainfrom
chrfalch/prebuilt-resources

Conversation

@chrfalch

@chrfalch chrfalch commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Depends on #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

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.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 22, 2026
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 9b0b73d to 2c1bf1d Compare June 22, 2026 16:45
@chrfalch chrfalch changed the title Chrfalch/prebuilt resources refactor(iOS): include resources like bundles into the precompiled XCFrameworks Jun 22, 2026
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch 6 times, most recently from c2c6397 to 2540bdc Compare June 24, 2026 06:55
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 2540bdc to 72768b5 Compare June 25, 2026 12:27
@chrfalch chrfalch marked this pull request as ready for review June 25, 2026 16:20
@chrfalch chrfalch requested a review from cipolleschi June 25, 2026 16:58
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jun 25, 2026
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch 6 times, most recently from 6255670 to 8158f41 Compare July 3, 2026 21:49
chrfalch added a commit to expo/expo that referenced this pull request Jul 4, 2026
…S overlay

## Summary

In RN 0.87 we'll hopefully release a new version of our precompiled binaries that works without the VFS overlay:

- react/react-native#57285 - removal of VFS overlay
- react/react-native#57305 - moved resource bundles to xcframework

Meaning that we'll get completely self-contained XCFrameworks from RN which is a prerequisite for SwiftPM to not become too complex.

## How

This PR addds support for detecting wether we're running with an RN version that uses these new frameworks or not - making sure we fallback to the old version when running with pre 0.87.

In addition it updates the pod install pipeline with the same tests.

NOTE: When merging 0.87 into Expo (as our current React Native), we can remove a lot of code in a follow-up PR that removes the VFS support all-over.

There are a couple of 3rd party packages not compatible with RN 0.87 yet, so I tested with some patches for these packages. In addition a small binary fix was added to fix a missing method in 0.87 in EXpo's code.

## Test-plan

- ✅ Built Bare-Expo, minimal-tester and precompiled with the new binaries from React native
- ✅ Verified that the same versions are using VFS and precompiling correctly when using RN 0.86
chrfalch and others added 5 commits July 6, 2026 18:35
The minimal machinery to build the packaged header structures:

- headers-spec.js: the executable layout contract (rules R1-R8) — which
  namespaces are hoisted into the React framework, which carry module maps,
  and how collisions are rejected.
- headers-inventory.js: scans the source tree and classifies every shipped
  header (language surface + modularizability bucket) — the input to the spec.
  computeInventory() feeds the build in-memory; the CLI writes a JSON manifest.
- headers-compose.js: emits the layout — writes the <React/...> headers +
  umbrella + module map into each React.framework slice (detected by the
  framework's presence), and assembles the headers-only
  ReactNativeHeaders.xcframework (every other namespace + deps + Hermes).
  Called by xcframework.js during compose.

This is the alternative header source that lets consumers resolve React Native
headers without a clang VFS overlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Emit the headers-spec layout unconditionally and delete the VFS overlay across
JS, CI publish, and Ruby. Consumers resolve headers the way the SwiftPM branch
does: <React/...> from the vendored React.framework, every other namespace from
ReactNativeHeaders. No root Headers/ on the xcframework, no VFS.

JS:
- xcframework.js: always emit the React.framework spec layout and build
  ReactNativeHeaders.xcframework (was gated behind RN_ZERO_I_LAYOUT=1). Remove
  the legacy header path entirely — the podspec->root-Headers enumeration,
  createModuleMapFile, and copyHeaderFilesToSlices — so the published
  React.xcframework is a standard framework (Info.plist + per-slice
  React.framework/{Headers,Modules}), no root Headers/ or Modules/. Ship
  ReactNativeHeaders.xcframework inside the reactnative-core tarball (sibling of
  React.xcframework) so the prebuilt pod can vend both; keep the standalone
  ReactNativeHeaders.xcframework.tar.gz for the SPM path. Drop the
  React-VFS-template.yaml emit and the ./vfs import.
- vfs.js: deleted (its only consumer was xcframework.js).
- types.js: drop the now-unused VFSEntry/VFSOverlay/HeaderMapping types.
- replace-rncore-version.js: drop the React-VFS.yaml preservation rationale.

Ruby/CocoaPods:
- React-Core-prebuilt.podspec: vend React.xcframework (its per-slice
  React.framework + module map serves <React/...> and @import React via
  FRAMEWORK_SEARCH_PATHS); flatten ReactNativeHeaders' headers into a top-level
  Headers/ in prepare_command and expose them via the pod header search path.
  Drop the VFS-era root header_mappings_dir/module_map.
- rncore.rb: remove the -ivfsoverlay injection and process_vfs_overlay;
  add_rncore_dependency and configure_aggregate_xcconfig now add a
  HEADER_SEARCH_PATHS to React-Core-prebuilt/Headers for podspec, aggregate, and
  third-party targets.
- react_native_pods.rb: drop the process_vfs_overlay post-install call.

Docs: replace the "VFS Overlay System" section with the headers-spec layout;
drop the obsolete "Known Issues" (pre-headers-spec) section.

Verified end-to-end: prebuild compose produces a VFS-free, root-Headers-free
React.xcframework; rn-tester pod install + xcodebuild (prebuilt path) BUILD
SUCCEEDED with zero -ivfsoverlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React.framework is a clang module; when an SPM consumer precompiles it, a
modular React/ header that #imports <react/...> hit
-Wnon-modular-include-in-framework-module because the lowercase react/
namespace (served from ReactNativeHeaders, per R1's Linux/Windows-safe layout)
was deliberately kept out of any module.

Give react/ a module where it already lives instead of relocating it (relocation
would require case-folding react.framework -> React.framework, which only works
on case-insensitive filesystems):

- headers-spec.js: drop the react/ namespace-module exemption so its
  objc-modular-candidates get a module; emit that module as
  ReactNativeHeaders_react (a module literally named 'react' would alias the
  React framework module on a case-insensitive filesystem). Module names are
  internal; <react/...> still resolves by header path and is now modular.
- headers-inventory.js: classify C++ default member initializers in aggregates
  (e.g. struct { NSString *family = nil; } in RCTFontProperties.h) as ObjC++ so
  these are not misclassified objc-modular-candidate and pulled into a plain
  ObjC module they cannot compile in.

The unguarded ObjC/C react/ headers (e.g. JSRuntimeFactoryCAPI.h) now resolve
modularly; the C++ react/renderer/* includes are #ifdef __cplusplus-guarded and
skipped during the ObjC module emit, so they need no module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In prebuilt mode the React core pods' code + headers live entirely in
React.xcframework / React-Core-prebuilt. Re-installing their SOURCE podspecs
made them ship duplicate headers that shadow the prebuilt artifact and break
the React framework's clang explicit-module precompile
(-Wnon-modular-include-in-framework-module) under Xcode 26.

Install those core pods as dependency-only FACADES instead: generated podspecs
with no sources/headers, installed via :path (so nothing is fetched), each
depending on React-Core-prebuilt. Version, subspecs, default_subspec and
resources (e.g. the privacy manifest) are DERIVED from the real podspec so the
facade stays graph- and resource-equivalent to the source pod.

With the shadowing gone the React module precompiles cleanly with
SWIFT_ENABLE_EXPLICIT_MODULES on, so the Xcode-26 workaround (#53457) is
removed. The prebuilt header search path + ReactNativeHeaders module-map
activation are consolidated into a single post-install injection site
(configure_aggregate_xcconfig); add_rncore_dependency now only declares the
React-Core-prebuilt dependency.

rn-tester's NativeComponentExample uses the canonical <React/...> include for
RCTFabricComponentsPlugins.h (resolved from the framework) so it builds against
the facaded React-RCTFabric in prebuilt mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`yarn format-check` (prettier) was failing CI on PR #57285. Run prettier on the
ios-prebuild headers scripts (headers-compose.js, headers-inventory.js),
replace-rncore-version.js, and __docs__/README.md so format-check passes. No
logic changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/react-native/React-Core.podspec Outdated
);
if (privacyManifest != null) {
fs.writeFileSync(
path.join(fwk, 'PrivacyInfo.xcprivacy'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major: Embedding PrivacyInfo.xcprivacy and RCTI18nStrings.bundle inside each slice's React.framework only reaches the app if the prebuilt is consumed as a dynamic framework. Under static linkage Xcode won't copy the wrapper's nested resources, so RCTLocalizedStringFromKey silently falls back to the untranslated defaultValue and the privacy manifest isn't aggregated. dSYM handling suggests dynamic linkage, but the failure mode is silent — worth confirming/documenting that the prebuilt is always consumed dynamically.

Comment thread packages/react-native/scripts/ios-prebuild/headers-compose.js
Comment thread packages/react-native/scripts/ios-prebuild/framework-resources.js Outdated
Comment thread packages/react-native/scripts/ios-prebuild/framework-resources.js Outdated
Comment thread packages/react-native/scripts/ios-prebuild/framework-resources.js
Comment thread packages/react-native/scripts/ios-prebuild/headers-compose.js Outdated

'use strict';

const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test gap: serializePrivacyManifest/readPrivacyManifest have no round-trip test (so a plist.build shape regression in the actual bytes written would pass CI), there's no integration test that resources land in each slice, and the RCTLocalizedString.mm loader (the highest-risk code given the static/dynamic concern) is untested.

Comment thread packages/react-native/scripts/ios-prebuild/framework-resources.js
Comment thread packages/react-native/React-Core.podspec
# Both bundles in one declaration: a second `resource_bundle(s) =` would replace
# (not merge) the first. RCTI18nStrings holds React-Core's localized strings
# (loaded by RCTLocalizedString); React-Core_privacy is the privacy manifest.
# (Prebuilt/SwiftPM get both from inside React.xcframework instead — see

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking — needs verification before merge.

This design ships the privacy manifest and RCTI18nStrings.bundle only from inside React.xcframework's React.framework; the prebuilt facade carries no pod-level resource_bundles/resources (see rncore_facades.rb: "the facade carries NO resources"). We still install the prebuilt via CocoaPods, and most apps link their pod graph statically (the New Architecture default: static libraries, no use_frameworks!).

CocoaPods copies resources nested inside a .framework only when it embeds that framework. It does embed vendored dynamic frameworks (via the [CP] Embed Pods Frameworks phase), and the prebuilt is currently built type: .dynamic (scripts/releases/ios-prebuild/swift-package.js), so in principle the nested bundle should be copied even in a static-pods app. But this is silent if it breaks — you'd get untranslated strings (RCTLocalizedStringFromKey falling back to the English default) and a missing PrivacyInfo.xcprivacy in the app's privacy report, with no build error.

Could we verify on a real app with the default CocoaPods setup (static libraries, no use_frameworks!) that:

  1. React.framework/RCTI18nStrings.bundle is actually copied into App.app/Frameworks/React.framework/ and RCTLocalizedStringFromKey returns translated strings, and
  2. PrivacyInfo.xcprivacy is aggregated into the generated privacy report?

Also worth checking use_frameworks! :linkage => :static and app-extension / Mac Catalyst targets, which don't always get an embed-frameworks phase.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified on a real app against the locally-built prebuilt, in both configurations you asked for plus an archive — harness and logs retained.

1. Default CocoaPods (static libraries, no use_frameworks!): App.app/Frameworks/React.framework/ contains RCTI18nStrings.bundle (all 37 lprojs) and PrivacyInfo.xcprivacy; codesign --verify --deep is clean on the embedded framework. CocoaPods embeds vendored dynamic frameworks via [CP] Embed Pods Frameworks regardless of the pod graph's linkage, and the embed carries nested resources. A runtime probe replicating RCTI18nStringsBundle()'s exact chain resolves both resources from the embedded framework at launch (codeBundle=…/HelloWorld.app/Frameworks/React.framework, locales=37).

2. use_frameworks! :linkage => :static: identical results on every check — the linkage option affects how CocoaPods builds source pods; a vendored dynamic binary is still embedded.

3. Privacy aggregation: PrivacyInfo.xcprivacy sits at the embedded framework root inside the .xcarchive's app — the documented input for Apple's report aggregation (Organizer's "Generate Privacy Report" itself is GUI-only, so manifest-in-archive is the automatable evidence).

4. Extensions / Catalyst: app extensions intentionally get no embed phase of their own — the framework embeds once into the host app and extensions load it via @rpath, and bundleForClass: resolves to wherever the code actually loaded from. The maccatalyst slice carries its own copies of both resources.

On "returns translated strings" — a finding worth having on the record: this is not demonstrable on OSS with or without this PR, because every Localizable.strings under React/I18n/strings/*.lproj (on main too) is a generated stub with zero key/value pairs; the fbt binary packs are only consumed by the internal WITH_FBI18N path. OSS RCTLocalizedStringFromKey has therefore always returned the untranslated default, and this PR preserves exactly that behavior while making the bundle (and any future translations) actually ship. The probe proves the resolution mechanism end-to-end: the default came back from the resolved bundle's localizedStringForKey:, not from the bundle-missing fallback.

To de-silence the failure mode you flagged, I've also pushed a hardening commit: RCTI18nStringsBundle() now emits a dev-only, once-per-process RCTLogWarn when the bundle cannot be resolved, naming both consequences (untranslated strings + manifest missing from the privacy report). Release builds are unchanged.

@cipolleschi cipolleschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that localization and privacy manifest keep working with this setup. We can't break the statically linked workflow because most users uses that workflow.

…den compose freshness + shell usage

Review feedback on the header modularization (VFS-overlay removal):

- computeInventory() now returns natural-path collisions; computeSpecPlan()
  fails closed on them (R8) so two sources projecting to the same Headers/
  path can no longer be silently merged (only identities[0].source was kept).
- ensureHeadersLayout()'s freshness marker folds a sha256 of the compose
  tooling (headers-{inventory,spec,compose}.js) so a local script edit forces
  a recompose — the source xcframework's Info.plist mtime alone couldn't.
- scanHeader() strips /* ... */ block comments (multi-line aware) so a
  documentation line mentioning namespace/template/constexpr can't trip the
  C++ detector and shrink the umbrella.
- Anonymous aggregates with C++ member initializers (typedef struct { x = ..; })
  are now detected as ObjC++ (tag name made optional).
- Multi-hyphen pod names namespaced correctly (.replace(/-/g,'_')).
- cp/tar shell-outs switched to execFileSync with arg arrays (CodeQL
  "command from environment values"; parity with headers-verify.js).
- Clearer hermes-missing warning; headers-spec R9/R10 added to the contract
  docblock (was R1–R8 then R11).
- New headers-inventory-test.js covering scanHeader (cxx guards, #else/#elif
  flips, anonymous aggregate, block/line comments).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 562985d to 3a57ac3 Compare July 9, 2026 06:00
@chrfalch

chrfalch commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks! Addressed in 3a57ac353ec.

Blocking (React-Core.podspec:127) — verified. Built the full CocoaPods matrix — 3 apps (HelloWorld / rn-tester / fresh-app) × {static, dynamic} × {cp-xcframework, cp-source} — all green, including the default static-libraries, no-use_frameworks! setup. The prebuilt is type: .dynamic, so CocoaPods embeds it and copies the nested RCTI18nStrings.bundle + PrivacyInfo.xcprivacy even under static pods. (Same resolves the headers-compose.js:131 Major.)

Other fixes:

  • Stale see ios-prebuild/{i18n,privacy}.js pointers → framework-resources.js (podspec + rncore_facades.rb).
  • headers-compose.js:14 — confirmed ensureHeadersLayout strips _CodeSignature before emitting (R7: signed after compose), so no post-signature writes.
  • framework-resources.jsNSPrivacyAccessedAPITypeReasons no longer fabricated when a source omitted it (tracks key presence); NSPrivacyCollectedDataTypes dedup canonicalizes (sorted-key) so different key order still dedups; i18n bundle Info.plist gains CFBundleShortVersionString/CFBundleVersion.
  • readdirSync({recursive}) / cpSync — safe on the repo engine (node >= 22); no fallback needed.
  • i18n bundle now built once to a temp stage and cloned into each slice (no per-slice rebuild; log count fixed).

Test gap: added serialize→readPrivacyManifest round-trip, order-insensitive collected-type dedup, omitted-reasons-key regression, CFBundleVersion presence, and an emitReactFrameworkHeaders integration test asserting PrivacyInfo.xcprivacy + RCTI18nStrings.bundle land in every slice.

… sort-imports)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 3a57ac3 to 8b50e2c Compare July 9, 2026 07:43
/bin/cp -c (APFS clonefile) is macOS-only; Linux CI exercises these
paths through the jest integration tests, where GNU cp rejects it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 8b50e2c to ca08afa Compare July 9, 2026 08:10
Comment thread packages/react-native/scripts/ios-prebuild/framework-resources.js
chrfalch and others added 6 commits July 10, 2026 13:43
…oolchain calls

The xcrun/clang/libtool/lipo/xcodebuild invocations in
buildReactNativeHeadersXcframework used execSync with template-interpolated
double-quoted paths; a path containing a quote or dollar sign could break or
shell-expand. Converted to execFileSync argument arrays (matching
headers-verify.js and the existing cp calls); libtool's 2>/dev/null shell
redirect becomes stdio: ['ignore', 'pipe', 'ignore'].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…prebuilt/SwiftPM

Source builds get React-Core's non-header resources from the podspec
resource_bundles, but in the prebuilt path the source pods aren't installed
(CocoaPods facades) / not present (SwiftPM), so they were lost. Reproduce them in
the artifact at compose time via scripts/ios-prebuild/framework-resources.js:

  - Privacy manifest: merge the PrivacyInfo.xcprivacy of the pods baked into
    React.framework into one manifest at the framework root, where Xcode's
    privacy-report aggregation picks it up (React.framework is dynamic; no runtime).
  - RCTI18nStrings: rebuild RCTI18nStrings.bundle from React/I18n/strings/*.lproj
    inside React.framework, resolved at runtime by the framework-aware loader.

RCTLocalizedString.mm now resolves the strings bundle from the code's own bundle
first (React.framework when prebuilt/SwiftPM) with a main-bundle fallback (static
source builds), keeping the graceful nil -> default behaviour.

React-Core.podspec declares RCTI18nStrings and React-Core_privacy in one
resource_bundles map (a later resource_bundles= had silently overwritten the
first), so source builds ship both again. The RNCore facade no longer carries
React-Core_privacy: the prebuilt artifact owns these resources now.

Red/green unit + integration tests in __tests__/framework-resources-test.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The FACADE_REEXPOSED_HEADERS loop passed an undefined `podspec_dir` to
copy_reexposed_headers, crashing `pod install` at `use_react_native!`
("undefined local variable or method 'podspec_dir'"). Derive it from the
real podspec path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback on embedding React.framework's non-header resources:

- Privacy-manifest merge no longer fabricates an empty NSPrivacyAccessedAPITypeReasons
  key when a source omitted it (tracks per-category key presence), so a single
  manifest truly passes through unchanged.
- NSPrivacyCollectedDataTypes dedup canonicalizes (recursively sorts) keys before
  hashing, so two pods declaring the same dict in different key order dedup.
- RCTI18nStrings.bundle is now built ONCE into a temp stage and cloned into each
  slice (mirrors the privacy manifest) instead of rebuilt inside the slice loop;
  i18nLocales is computed once.
- i18n bundle Info.plist gains CFBundleShortVersionString / CFBundleVersion so
  Apple validation tooling doesn't warn on a version-less bundle.
- Fixed stale "see" pointers ios-prebuild/{i18n,privacy}.js -> framework-resources.js
  (React-Core.podspec + rncore_facades.rb).
- Tests: serialize->readPrivacyManifest round-trip, order-insensitive collected-type
  dedup, omitted-reasons-key regression, CFBundleVersion presence, and an
  emitReactFrameworkHeaders integration test asserting PrivacyInfo.xcprivacy +
  RCTI18nStrings.bundle land in EVERY slice.

Note: resources are written after _CodeSignature is stripped (R7 — signed after
compose), so no signature invalidation; readdirSync({recursive})/cpSync are safe on
the repo's Node >=22 engine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mixed is now a deprecated utility type (use unknown), and the
NSPrivacyAccessedAPITypeReasons refinement was invalidated by the
intervening Map.get() call — hoist it into a local first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dle clone

Same macOS-only clonefile issue as the other copy sites; this one is on
the resource-landing path the jest integration test exercises on Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from ca08afa to 7b29638 Compare July 10, 2026 11:49
chrfalch and others added 2 commits July 10, 2026 14:18
The resources shipped inside the prebuilt React.framework fail SILENTLY
if the framework is ever consumed without being embedded: every localized
string falls back to its untranslated default and the privacy manifest
drops out of the app's aggregated privacy report, with no build error.
Emit a dev-only, once-per-process RCTLogWarn when the bundle resolves nil
so an embed regression is observable in every dev console instead of
shipping quietly.

No behavior change in release (RCT_DEV compiled out) or on the happy path.
Not unit-testable (ObjC runtime diagnostics, no jest surface); both
RCT_DEV and NDEBUG preprocessing paths compile-verified via clang
-fsyntax-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… manifests

collectReactPrivacyManifestPaths merged ANY PrivacyInfo.xcprivacy found
recursively under the React privacy roots — a latent trap: a future pod
under those roots that ships as its OWN framework would silently have its
manifest folded into React.framework's aggregate (over-declaration in the
app's privacy report). The scan is now validated against an explicit
REACT_PRIVACY_MANIFESTS allowlist and FAILS the prebuild on an unlisted
manifest, with instructions for both resolutions. Listed-but-absent stays
legal: partial fixture trees keep working, an upstream deletion
under-declares exactly as a source build would, and a MOVED manifest
cannot slip through since its new path is unlisted.

Also: only create the i18n temp stage when there are .lproj dirs to
bundle, and document that the dedup key's `?? ''` is Flow appeasement
(JSON.stringify is typed `string | void`), not dead code.

56 ios-prebuild tests green (drift-gate cases red-first); Flow + prettier
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@meta-codesync

meta-codesync Bot commented Jul 10, 2026

Copy link
Copy Markdown

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D111448862.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants