react-native 0.87.0-rc.1 → 0.87.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/Libraries/Core/InitializeCore.js +8 -1
  2. package/Libraries/Core/ReactNativeVersion.js +1 -1
  3. package/Libraries/ReactNative/AppRegistry.flow.js +1 -0
  4. package/Libraries/ReactNative/AppRegistryImpl.js +2 -3
  5. package/React/Base/RCTVersion.m +1 -1
  6. package/React/I18n/RCTLocalizedString.mm +38 -2
  7. package/React-Core-prebuilt.podspec +45 -17
  8. package/React-Core.podspec +9 -2
  9. package/ReactAndroid/external-artifacts/build.gradle.kts +49 -0
  10. package/ReactAndroid/gradle.properties +1 -1
  11. package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SynchronousMountItem.kt +8 -2
  12. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  13. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  14. package/package.json +10 -8
  15. package/react-native.config.js +81 -0
  16. package/scripts/cocoapods/fabric.rb +1 -1
  17. package/scripts/cocoapods/rncore.rb +35 -78
  18. package/scripts/cocoapods/rncore_facades.rb +232 -0
  19. package/scripts/cocoapods/rndependencies.rb +64 -3
  20. package/scripts/cocoapods/rndeps_facades.rb +193 -0
  21. package/scripts/cocoapods/spm.rb +78 -11
  22. package/scripts/codegen/templates/Package.swift.spm-template +97 -0
  23. package/scripts/react-native-xcode.sh +20 -0
  24. package/scripts/react_native_pods.rb +65 -16
  25. package/scripts/replace-rncore-version.js +53 -6
  26. package/scripts/setup-apple-spm.js +1165 -0
  27. package/scripts/spm/__doc__/rfc-spm-xcframework.md +707 -0
  28. package/scripts/spm/__doc__/spm-autolinking-plugins.md +244 -0
  29. package/scripts/spm/__doc__/spm-header-paths-contract.md +97 -0
  30. package/scripts/spm/__doc__/spm-plugins-assessment.md +128 -0
  31. package/scripts/spm/__doc__/spm-scripts.md +451 -0
  32. package/scripts/spm/autolinking-plugins.js +331 -0
  33. package/scripts/spm/download-spm-artifacts.js +1409 -0
  34. package/scripts/spm/expand-spm-dependencies.js +216 -0
  35. package/scripts/spm/flavored-frameworks.js +1008 -0
  36. package/scripts/spm/generate-spm-autolinking-config.js +161 -0
  37. package/scripts/spm/generate-spm-autolinking.js +1888 -0
  38. package/scripts/spm/generate-spm-package.js +302 -0
  39. package/scripts/spm/generate-spm-xcodeproj.js +2224 -0
  40. package/scripts/spm/read-podspec.js +695 -0
  41. package/scripts/spm/scaffold-package-swift.js +1206 -0
  42. package/scripts/spm/spm-pbxproj.js +654 -0
  43. package/scripts/spm/spm-types.js +517 -0
  44. package/scripts/spm/spm-utils.js +645 -0
  45. package/scripts/spm/sync-spm-autolinking.js +160 -0
  46. package/sdks/.hermesv1version +1 -0
  47. package/sdks/hermes-engine/utils/replace_hermes_version.js +18 -4
  48. package/sdks/hermes-engine/version.properties +1 -1
  49. package/third-party-podspecs/ReactNativeDependencies.podspec +2 -2
  50. package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +2 -2
  51. package/Libraries/Utilities/SceneTracker.js +0 -42
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ * @format
9
+ */
10
+
11
+ /**
12
+ * SwiftPM autolinking plugins — the extension seam for frameworks (Expo first)
13
+ * that need to contribute their own SwiftPM package refs / product deps /
14
+ * generated sources into the autolinked package graph.
15
+ *
16
+ * PREVIEW / UNSTABLE CONTRACT. The discovery mechanism and the plugin
17
+ * function's context/return shape may change while the first real consumers
18
+ * (Expo) validate it.
19
+ *
20
+ * Why a plugin and not a post-process: the Xcode auto-sync build phase
21
+ * re-runs autolinking on every dependency change, so a one-shot rewrite of the
22
+ * generated Package.swift is clobbered on the next sync. Plugins are invoked
23
+ * from generate-spm-autolinking.js:main() — the single function both `add`/
24
+ * `update` and the build-time `sync` call — so a contribution runs on EVERY
25
+ * regeneration.
26
+ *
27
+ * Discovery (transitive, mirrors CocoaPods' `use_expo_modules!`): any
28
+ * autolinked dependency can register a plugin from its own
29
+ * react-native.config.js —
30
+ *
31
+ * // node_modules/expo/react-native.config.js
32
+ * module.exports = { spm: { autolinkingPlugin: './spm/plugin.js' } };
33
+ *
34
+ * Installing the framework is enough; no app-level config is required. An app
35
+ * MAY still exclude a plugin via `spm.denyPlugins` (npm-name list) in its own
36
+ * react-native.config.js — an escape hatch, not a required allowlist.
37
+ *
38
+ * Contract:
39
+ *
40
+ * module.exports = function plugin(context) {
41
+ * // context: {appRoot, projectRoot, reactNativeRoot, autolinking,
42
+ * // outputDir, react}
43
+ * // context.react (?ReactDescriptor): how to depend on React —
44
+ * // {packageRef, products}. packageRef is local ({name, path (absolute),
45
+ * // relPath}) or remote ({name, url, version}); products is the set RN
46
+ * // gives its own autolinked targets (incl. ReactAppHeaders from the
47
+ * // separate React-GeneratedCode package), filtered to what resolves
48
+ * // this run. Use it instead of re-deriving RN's path/identity/products.
49
+ * return {
50
+ * packageDependencies: [{name, path}] | [{name, url, version}],
51
+ * productDependencies: [{name, package}],
52
+ * // Sources compiled INTO THE APP TARGET (not the static Autolinked
53
+ * // aggregate) — the only place an @objc class reaches the ObjC
54
+ * // classlist, so emit files here when a class must be reachable by
55
+ * // name (e.g. via NSClassFromString), as Expo's ExpoModulesProvider
56
+ * // needs. Recorded to `.spm-plugin-generated-sources.json`; the
57
+ * // `spm add`/`update` xcodeproj injector wires each into the app
58
+ * // target (PBXFileReference + Sources-phase entry), marker-tracked
59
+ * // so deinit reverts and update reconciles.
60
+ * generatedSources: [{path}],
61
+ * // Precompiled dynamic frameworks selected and embedded by RN outside
62
+ * // the SwiftPM graph. Both flavors are mandatory.
63
+ * flavoredFrameworks: [{id, frameworkName, linkage: 'dynamic',
64
+ * flavors: {debug, release}}],
65
+ * // Absolute paths (dirs OR files) the Xcode auto-sync build phase
66
+ * // should watch for staleness — e.g. the plugin dep's own
67
+ * // `Package.swift`, `expo-module.config.json`, and per-module
68
+ * // manifests. Folded into `.spm-sync-watch-paths`; a file edit or a
69
+ * // dir add/remove there re-triggers the sync. Relative/empty/non-string
70
+ * // entries are dropped with a warning; a non-array is ignored.
71
+ * watchPaths: ['/abs/path/Package.swift', '/abs/dir'],
72
+ * };
73
+ * };
74
+ *
75
+ * The plugin returns DATA; it never writes into RN's generated tree. RN owns
76
+ * the merge, so regeneration stays deterministic and idempotent.
77
+ */
78
+
79
+ const path = require('path');
80
+
81
+ /*:: import type {
82
+ AutolinkedDep,
83
+ PluginContext,
84
+ PluginResult,
85
+ DiscoveredPlugin,
86
+ } from './spm-types';
87
+ */
88
+
89
+ /**
90
+ * Discover plugins declared by autolinked deps. `readConfig(root)` returns the
91
+ * dep's parsed react-native.config.js (or null). `denyList` is the app's
92
+ * `spm.denyPlugins` (npm names to skip). Fail-closed: a declared-but-missing
93
+ * or unloadable plugin throws, naming the dep — a framework silently dropping
94
+ * its modules is worse than a loud stop.
95
+ */
96
+ function discoverPlugins(
97
+ deps /*: ReadonlyArray<AutolinkedDep> */,
98
+ readConfig /*: (root: string) => ?{readonly [string]: unknown} */,
99
+ denyList /*: ReadonlyArray<string> */ = [],
100
+ ) /*: Array<DiscoveredPlugin> */ {
101
+ const denied = new Set(denyList);
102
+ const found /*: Array<DiscoveredPlugin> */ = [];
103
+ for (const dep of deps) {
104
+ if (denied.has(dep.name)) {
105
+ continue;
106
+ }
107
+ const config = readConfig(dep.root);
108
+ // $FlowFixMe[incompatible-use] config has a dynamic shape
109
+ const rel = config?.spm?.autolinkingPlugin;
110
+ if (rel == null) {
111
+ continue;
112
+ }
113
+ if (typeof rel !== 'string' || rel.length === 0) {
114
+ throw new Error(
115
+ `react-native spm: '${dep.name}' declares an invalid spm.autolinkingPlugin ` +
116
+ `(expected a module path string).`,
117
+ );
118
+ }
119
+ const pluginPath = path.resolve(dep.root, rel);
120
+ let fn: unknown;
121
+ try {
122
+ // $FlowFixMe[unsupported-syntax] dynamic require by computed path
123
+ fn = require(pluginPath);
124
+ } catch (e) {
125
+ throw new Error(
126
+ `react-native spm: failed to load the autolinking plugin for '${dep.name}' ` +
127
+ `at ${pluginPath}: ${e.message}`,
128
+ );
129
+ }
130
+ // Support `module.exports = fn` and `{default: fn}` / `{plugin: fn}`.
131
+ const resolved =
132
+ typeof fn === 'function'
133
+ ? fn
134
+ : // $FlowFixMe[incompatible-use] interop shapes
135
+ typeof fn?.default === 'function'
136
+ ? fn.default
137
+ : // $FlowFixMe[incompatible-use]
138
+ typeof fn?.plugin === 'function'
139
+ ? fn.plugin
140
+ : null;
141
+ if (resolved == null) {
142
+ throw new Error(
143
+ `react-native spm: the autolinking plugin for '${dep.name}' at ${pluginPath} ` +
144
+ `does not export a function.`,
145
+ );
146
+ }
147
+ found.push({depName: dep.name, pluginPath, plugin: resolved});
148
+ }
149
+ return found;
150
+ }
151
+
152
+ /**
153
+ * Invoke discovered plugins and merge their results. Each plugin gets the same
154
+ * context. Fail-closed: a throwing plugin aborts (named), and a malformed
155
+ * return is rejected. Dedupe package + product deps by name so a plugin and an
156
+ * npm dep that both reference the same package don't double-declare.
157
+ */
158
+ function invokePlugins(
159
+ plugins /*: ReadonlyArray<DiscoveredPlugin> */,
160
+ context /*: PluginContext */,
161
+ logger /*: {warn: (msg: string) => void} */ = {
162
+ warn: msg => console.warn(msg),
163
+ },
164
+ ) /*: PluginResult */ {
165
+ const packageDependencies /*: Array<{name: string, path?: string, url?: string, version?: string}> */ =
166
+ [];
167
+ const productDependencies /*: Array<{name: string, package: string}> */ = [];
168
+ const generatedSources /*: Array<{path: string}> */ = [];
169
+ const flavoredFrameworks /*: Array<{id: string, frameworkName: string, linkage: 'dynamic', flavors: {debug: string, release: string}}> */ =
170
+ [];
171
+ const watchPaths /*: Array<string> */ = [];
172
+ const seenPackages /*: Set<string> */ = new Set();
173
+ const seenProducts /*: Set<string> */ = new Set();
174
+ const seenFrameworkIds /*: Set<string> */ = new Set();
175
+ const seenFrameworkNames /*: Set<string> */ = new Set();
176
+
177
+ for (const {depName, pluginPath, plugin} of plugins) {
178
+ let result: unknown;
179
+ try {
180
+ result = plugin(context);
181
+ } catch (e) {
182
+ throw new Error(
183
+ `react-native spm: the autolinking plugin for '${depName}' (${pluginPath}) ` +
184
+ `threw: ${e.message}`,
185
+ );
186
+ }
187
+ if (result == null) {
188
+ continue;
189
+ }
190
+ if (typeof result !== 'object') {
191
+ throw new Error(
192
+ `react-native spm: the autolinking plugin for '${depName}' returned a ` +
193
+ `${typeof result}; expected an object or undefined.`,
194
+ );
195
+ }
196
+ // $FlowFixMe[incompatible-use] validated field-by-field below
197
+ const pkgs = result.packageDependencies ?? [];
198
+ // $FlowFixMe[incompatible-use]
199
+ const prods = result.productDependencies ?? [];
200
+ // $FlowFixMe[incompatible-use]
201
+ const srcs = result.generatedSources ?? [];
202
+ // $FlowFixMe[incompatible-use]
203
+ const rawFrameworks = result.flavoredFrameworks ?? [];
204
+ // $FlowFixMe[incompatible-use]
205
+ const rawWatch = result.watchPaths ?? [];
206
+ if (!Array.isArray(rawFrameworks)) {
207
+ throw new Error(
208
+ `react-native spm: '${depName}' returned a non-array flavoredFrameworks.`,
209
+ );
210
+ }
211
+ // Watch paths are best-effort staleness hints, so
212
+ // a non-array is ignored (warn, never fatal).
213
+ if (!Array.isArray(rawWatch)) {
214
+ logger.warn(
215
+ `react-native spm: '${depName}' returned a non-array watchPaths ` +
216
+ `— ignoring it.`,
217
+ );
218
+ }
219
+ const watch = Array.isArray(rawWatch) ? rawWatch : [];
220
+ for (const p of pkgs) {
221
+ if (p == null || typeof p.name !== 'string') {
222
+ throw new Error(
223
+ `react-native spm: '${depName}' returned a packageDependency without a name.`,
224
+ );
225
+ }
226
+ if (p.path == null && (p.url == null || p.version == null)) {
227
+ throw new Error(
228
+ `react-native spm: '${depName}' packageDependency '${p.name}' needs either ` +
229
+ `a path or a url+version.`,
230
+ );
231
+ }
232
+ if (!seenPackages.has(p.name)) {
233
+ seenPackages.add(p.name);
234
+ packageDependencies.push(p);
235
+ }
236
+ }
237
+ for (const p of prods) {
238
+ if (
239
+ p == null ||
240
+ typeof p.name !== 'string' ||
241
+ typeof p.package !== 'string'
242
+ ) {
243
+ throw new Error(
244
+ `react-native spm: '${depName}' returned a productDependency needing name + package.`,
245
+ );
246
+ }
247
+ const key = `${p.package}/${p.name}`;
248
+ if (!seenProducts.has(key)) {
249
+ seenProducts.add(key);
250
+ productDependencies.push(p);
251
+ }
252
+ }
253
+ for (const s of srcs) {
254
+ if (s == null || typeof s.path !== 'string') {
255
+ throw new Error(
256
+ `react-native spm: '${depName}' returned a generatedSource without a path.`,
257
+ );
258
+ }
259
+ generatedSources.push(s);
260
+ }
261
+ for (const framework of rawFrameworks) {
262
+ if (
263
+ framework == null ||
264
+ typeof framework.id !== 'string' ||
265
+ !/^[A-Za-z0-9_.-]+$/.test(framework.id) ||
266
+ typeof framework.frameworkName !== 'string' ||
267
+ framework.frameworkName.length === 0 ||
268
+ framework.linkage !== 'dynamic' ||
269
+ framework.flavors == null ||
270
+ typeof framework.flavors.debug !== 'string' ||
271
+ typeof framework.flavors.release !== 'string' ||
272
+ !path.isAbsolute(framework.flavors.debug) ||
273
+ !path.isAbsolute(framework.flavors.release)
274
+ ) {
275
+ throw new Error(
276
+ `react-native spm: '${depName}' returned an invalid flavoredFramework ` +
277
+ '(need {id, frameworkName, linkage: "dynamic", flavors: ' +
278
+ '{debug, release}} with absolute flavor paths).',
279
+ );
280
+ }
281
+ if (seenFrameworkIds.has(framework.id)) {
282
+ throw new Error(
283
+ `react-native spm: duplicate flavored framework id '${framework.id}'.`,
284
+ );
285
+ }
286
+ if (seenFrameworkNames.has(framework.frameworkName)) {
287
+ throw new Error(
288
+ `react-native spm: multiple plugins embed '${framework.frameworkName}.framework'.`,
289
+ );
290
+ }
291
+ seenFrameworkIds.add(framework.id);
292
+ seenFrameworkNames.add(framework.frameworkName);
293
+ flavoredFrameworks.push({
294
+ id: framework.id,
295
+ frameworkName: framework.frameworkName,
296
+ linkage: 'dynamic',
297
+ flavors: {
298
+ debug: framework.flavors.debug,
299
+ release: framework.flavors.release,
300
+ },
301
+ });
302
+ }
303
+ // watchPaths are dropped (with a warning), not fatal. A non-string, empty,
304
+ // or relative entry only
305
+ // means one staleness input is missed, not a broken build. Absolute-only so
306
+ // the generated phase (which has no cwd context) can test them directly.
307
+ for (const w of watch) {
308
+ if (typeof w !== 'string' || w.length === 0 || !path.isAbsolute(w)) {
309
+ logger.warn(
310
+ `react-native spm: '${depName}' returned an invalid watchPath ` +
311
+ `(need a non-empty absolute path string) — dropping it.`,
312
+ );
313
+ continue;
314
+ }
315
+ watchPaths.push(w);
316
+ }
317
+ }
318
+
319
+ return {
320
+ packageDependencies,
321
+ productDependencies,
322
+ generatedSources,
323
+ flavoredFrameworks,
324
+ watchPaths,
325
+ };
326
+ }
327
+
328
+ module.exports = {
329
+ discoverPlugins,
330
+ invokePlugins,
331
+ };