react-native 0.87.0-rc.0 → 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 (70) hide show
  1. package/Libraries/Core/InitializeCore.js +9 -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/Libraries/ReactPrivate/ReactNativePrivateInterface.js +5 -116
  6. package/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow +5 -31
  7. package/React/Base/RCTVersion.m +1 -1
  8. package/React/I18n/RCTLocalizedString.mm +38 -2
  9. package/React-Core-prebuilt.podspec +45 -17
  10. package/React-Core.podspec +9 -2
  11. package/ReactAndroid/external-artifacts/build.gradle.kts +49 -0
  12. package/ReactAndroid/gradle.properties +1 -1
  13. package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SynchronousMountItem.kt +8 -2
  14. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  15. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  16. package/index.js +15 -3
  17. package/index.js.flow +0 -2
  18. package/package.json +27 -29
  19. package/react-native.config.js +81 -0
  20. package/scripts/cocoapods/fabric.rb +1 -1
  21. package/scripts/cocoapods/rncore.rb +35 -78
  22. package/scripts/cocoapods/rncore_facades.rb +232 -0
  23. package/scripts/cocoapods/rndependencies.rb +64 -3
  24. package/scripts/cocoapods/rndeps_facades.rb +193 -0
  25. package/scripts/cocoapods/spm.rb +78 -11
  26. package/scripts/codegen/templates/Package.swift.spm-template +97 -0
  27. package/scripts/react-native-xcode.sh +20 -0
  28. package/scripts/react_native_pods.rb +65 -16
  29. package/scripts/replace-rncore-version.js +53 -6
  30. package/scripts/setup-apple-spm.js +1165 -0
  31. package/scripts/spm/__doc__/rfc-spm-xcframework.md +707 -0
  32. package/scripts/spm/__doc__/spm-autolinking-plugins.md +244 -0
  33. package/scripts/spm/__doc__/spm-header-paths-contract.md +97 -0
  34. package/scripts/spm/__doc__/spm-plugins-assessment.md +128 -0
  35. package/scripts/spm/__doc__/spm-scripts.md +451 -0
  36. package/scripts/spm/autolinking-plugins.js +331 -0
  37. package/scripts/spm/download-spm-artifacts.js +1409 -0
  38. package/scripts/spm/expand-spm-dependencies.js +216 -0
  39. package/scripts/spm/flavored-frameworks.js +1008 -0
  40. package/scripts/spm/generate-spm-autolinking-config.js +161 -0
  41. package/scripts/spm/generate-spm-autolinking.js +1888 -0
  42. package/scripts/spm/generate-spm-package.js +302 -0
  43. package/scripts/spm/generate-spm-xcodeproj.js +2224 -0
  44. package/scripts/spm/read-podspec.js +695 -0
  45. package/scripts/spm/scaffold-package-swift.js +1206 -0
  46. package/scripts/spm/spm-pbxproj.js +654 -0
  47. package/scripts/spm/spm-types.js +517 -0
  48. package/scripts/spm/spm-utils.js +645 -0
  49. package/scripts/spm/sync-spm-autolinking.js +160 -0
  50. package/sdks/.hermesv1version +1 -0
  51. package/sdks/hermes-engine/utils/replace_hermes_version.js +18 -4
  52. package/sdks/hermes-engine/version.properties +1 -1
  53. package/src/asset-registry.js +1 -1
  54. package/src/react-private-interface.js +145 -0
  55. package/src/react-private-interface.js.flow +48 -0
  56. package/src/setup-env.js +22 -0
  57. package/src/unstable-internals-do-not-use.d.ts +214 -0
  58. package/src/unstable-internals-do-not-use.js +76 -0
  59. package/third-party-podspecs/ReactNativeDependencies.podspec +2 -2
  60. package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +2 -2
  61. package/types_generated/Libraries/ReactPrivate/ReactNativePrivateInterface.d.ts +6 -21
  62. package/types_generated/index.d.ts +1 -2
  63. package/types_generated/src/private/renderer/events/dispatchNativeEvent.d.ts +26 -0
  64. package/types_generated/src/react-private-interface.d.ts +33 -0
  65. package/Libraries/Utilities/SceneTracker.js +0 -42
  66. package/jest-preset.js +0 -26
  67. package/rn-get-polyfills.js +0 -13
  68. package/types/tsconfig.json +0 -17
  69. package/types_generated/Libraries/Components/Touchable/Touchable.d.ts +0 -261
  70. package/types_generated/tsconfig.test.json +0 -17
@@ -0,0 +1,645 @@
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 strict-local
8
+ * @format
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Creates a logger trio {log, warn, die} that prefixes messages with [name].
19
+ * log – green prefix, writes to stdout
20
+ * warn – yellow prefix, writes to stderr
21
+ * die – red prefix, writes to stderr, sets exitCode=1, throws
22
+ */
23
+ function makeLogger(name /*: string */) /*: {
24
+ log: (msg: string) => void,
25
+ warn: (msg: string) => void,
26
+ die: (msg: string) => empty,
27
+ } */ {
28
+ // Prefix every newline-separated line of the message so multi-line output
29
+ // wraps cleanly when terminal log scrapers look for the `[name]` tag.
30
+ function format(color /*: string */, msg /*: string */) /*: string */ {
31
+ const prefix = `\x1b[${color}m[${name}]\x1b[0m`;
32
+ return msg
33
+ .split('\n')
34
+ .map(line => `${prefix} ${line}`)
35
+ .join('\n');
36
+ }
37
+ return {
38
+ log(msg /*: string */) /*: void */ {
39
+ console.log(format('32', msg));
40
+ },
41
+ warn(msg /*: string */) /*: void */ {
42
+ console.warn(format('33', msg));
43
+ },
44
+ die(msg /*: string */) /*: empty */ {
45
+ console.error(format('31', msg));
46
+ process.exitCode = 1;
47
+ throw new Error(msg);
48
+ },
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Returns a short, human-readable representation of an absolute path:
54
+ * - Paths under $HOME are shown as ~/...
55
+ * - Paths under cwd are shown as relative (if ≤2 levels up)
56
+ * - Otherwise the absolute path is returned unchanged
57
+ */
58
+ function displayPath(p /*: string */) /*: string */ {
59
+ const home = os.homedir();
60
+ if (p === home) return '~';
61
+ if (p.startsWith(home + path.sep)) {
62
+ return '~' + p.slice(home.length);
63
+ }
64
+ const rel = path.relative(process.cwd(), p);
65
+ if (rel && !rel.startsWith('../../..')) {
66
+ return rel;
67
+ }
68
+ return p;
69
+ }
70
+
71
+ /**
72
+ * Canonical React Native binary cache root. Mirrors CocoaPods'
73
+ * `ReactNativePodsUtils.shared_cache_dir()` (~/Library/Caches/ReactNative, added
74
+ * in #56847) so SPM and CocoaPods share one cache root — and so SPM stops using
75
+ * a `com.facebook.ReactNative` (bundle-id) dir that other tools may also touch.
76
+ * Honor `RCT_SKIP_CACHES=1` (same env var as CocoaPods) to bypass the shared
77
+ * tarball cache.
78
+ */
79
+ function sharedCacheDir() /*: string */ {
80
+ return path.join(os.homedir(), 'Library', 'Caches', 'ReactNative');
81
+ }
82
+
83
+ /**
84
+ * Returns the default versioned cache directory for SPM's EXTRACTED xcframeworks,
85
+ * nested under the canonical cache root. Downloaded tarballs themselves go in the
86
+ * flat shared cache (sharedCacheDir()) so they are reused across SPM/CocoaPods.
87
+ *
88
+ * @param {string} versionKey Version string used as directory name.
89
+ * Pass the raw --version arg (e.g. 'nightly') so the
90
+ * cache slot is stable regardless of the resolved hash.
91
+ * @param {string} flavor 'debug' or 'release'
92
+ */
93
+ function defaultCacheDir(
94
+ versionKey /*: string */,
95
+ flavor /*: string */,
96
+ ) /*: string */ {
97
+ return path.join(sharedCacheDir(), 'spm-artifacts', versionKey, flavor);
98
+ }
99
+
100
+ /**
101
+ * Sanitize a package/app name to a valid Swift identifier.
102
+ * e.g. "@react-native/tester" -> "RNTester", "my-app" -> "MyApp"
103
+ */
104
+ function toSwiftName(name /*: string */) /*: string */ {
105
+ const base = name.replace(/^@[^/]+\//, '');
106
+ return base
107
+ .split(/[^a-zA-Z0-9]+/)
108
+ .filter(Boolean)
109
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
110
+ .join('');
111
+ }
112
+
113
+ /**
114
+ * Derive a default app name from the raw package name and source path.
115
+ * Prefers the source directory name when it's meaningful (e.g. "RNTester"),
116
+ * falls back to the package name for generic dirs like "ios" or "src".
117
+ */
118
+ function deriveAppName(
119
+ rawName /*: string */,
120
+ sourcePath /*: string */,
121
+ ) /*: string */ {
122
+ const genericSourceDirs = new Set(['ios', 'app', 'sources', 'src']);
123
+ const cleanName = rawName.replace(/^@[^/]+\//, '');
124
+ return toSwiftName(
125
+ sourcePath !== toSwiftName(cleanName) &&
126
+ !genericSourceDirs.has(sourcePath.toLowerCase())
127
+ ? sourcePath
128
+ : cleanName,
129
+ );
130
+ }
131
+
132
+ // $FlowFixMe[unclear-type] JSON data has dynamic shape
133
+ function readPackageJson(dir /*: string */) /*: Object | null */ {
134
+ const pkgPath = path.join(dir, 'package.json');
135
+ if (!fs.existsSync(pkgPath)) {
136
+ return null;
137
+ }
138
+ // $FlowFixMe[incompatible-return] JSON.parse returns any
139
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
140
+ }
141
+
142
+ /**
143
+ * Walk up from startDir until we find a directory containing package.json.
144
+ * Returns startDir itself if it contains package.json, or startDir as fallback
145
+ * if no package.json is found anywhere up the tree.
146
+ */
147
+ function findProjectRoot(startDir /*: string */) /*: string */ {
148
+ const start = path.resolve(startDir);
149
+ let dir = start;
150
+ // Bounded by filesystem depth — path.dirname converges to '/' or 'C:\\'.
151
+ // The `dir = ...` updates would otherwise drop the start-fallback narrowing.
152
+ while (dir !== path.dirname(dir)) {
153
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
154
+ return dir;
155
+ }
156
+ dir = path.dirname(dir);
157
+ }
158
+ // At filesystem root — last check before falling back.
159
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
160
+ return dir;
161
+ }
162
+ return start;
163
+ }
164
+
165
+ /**
166
+ * Resolve the react-native package root from an app directory.
167
+ * Checks appRoot/projectRoot and their ancestors for node_modules/react-native,
168
+ * then falls back to __dirname-relative resolution (monorepo layout).
169
+ *
170
+ * Returns null if react-native cannot be found.
171
+ */
172
+ function resolveReactNativeRoot(
173
+ appRoot /*: string */,
174
+ projectRoot /*: string */,
175
+ ) /*: string | null */ {
176
+ const candidates /*: Array<string> */ = [];
177
+ const seen /*: Set<string> */ = new Set();
178
+
179
+ function addAncestorCandidates(startDir /*: string */) /*: void */ {
180
+ let dir = path.resolve(startDir);
181
+ while (true) {
182
+ const candidate = path.join(dir, 'node_modules', 'react-native');
183
+ if (!seen.has(candidate)) {
184
+ seen.add(candidate);
185
+ candidates.push(candidate);
186
+ }
187
+ const parent = path.dirname(dir);
188
+ if (parent === dir) {
189
+ break;
190
+ }
191
+ dir = parent;
192
+ }
193
+ }
194
+
195
+ addAncestorCandidates(appRoot);
196
+ addAncestorCandidates(projectRoot);
197
+ candidates.push(path.resolve(__dirname, '../..'));
198
+
199
+ for (const candidate of candidates) {
200
+ if (fs.existsSync(candidate)) {
201
+ return path.resolve(candidate);
202
+ }
203
+ }
204
+ return null;
205
+ }
206
+
207
+ // The per-app farm lives INSIDE the codegen package (build/generated/ios) so
208
+ // it can be vended as a normal SPM headers target ("ReactAppHeaders") — the
209
+ // farm reaches consumers via SPM product dependencies, not -I.
210
+ const PER_APP_HEADERS_REL = 'build/generated/ios/ReactAppHeaders';
211
+
212
+ // Marker at the top of a scaffolder-generated Package.swift. Lives here (not in
213
+ // scaffold-package-swift.js) so the autolinker can recognize scaffolded files
214
+ // without a circular import (scaffold-package-swift requires the autolinker).
215
+ const SCAFFOLDER_MARKER =
216
+ '// AUTO-SCAFFOLDED by react-native spm scaffold — safe to edit & commit via patch-package.';
217
+
218
+ // Remote SPM package mode (prototype of the GitHub-distribution endgame):
219
+ // when active, app + libraries all depend on ONE remote package identity
220
+ // (`.package(url:exact:)`) instead of the local path-based artifacts package
221
+ // — SPM unifies the version across the graph and the local compose/symlink
222
+ // machinery is skipped. Activated via RN_SPM_REMOTE_URL +
223
+ // RN_SPM_REMOTE_VERSION (persisted per-app so Xcode-phase re-syncs without
224
+ // the env keep the mode).
225
+ const REMOTE_CONFIG_REL = 'build/generated/autolinking/spm-remote.json';
226
+ function remotePackageIdentity(url /*: string */) /*: string */ {
227
+ const tail = url.replace(/\/+$/, '').split('/').pop() ?? '';
228
+ return tail.replace(/\.git$/, '').toLowerCase();
229
+ }
230
+
231
+ /**
232
+ * Thrown by remotePackageConfig when remote SPM mode is active (a URL is set)
233
+ * but no usable RN version can be determined: react-native isn't installed, or
234
+ * the installed version is a non-publishable dev placeholder (e.g. the monorepo
235
+ * '1000.0.0', which has no remote tag) and no override was supplied. Carries a
236
+ * developer-facing message; the CLI turns it into a hard build error (exit 2).
237
+ */
238
+ class RemoteVersionError extends Error {
239
+ constructor(message /*: string */) {
240
+ super(message);
241
+ this.name = 'RemoteVersionError';
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Resolve the version of the installed react-native by walking up from appRoot
247
+ * looking for node_modules/react-native/package.json. Mirrors the autolinker's
248
+ * appRoot-first, up-to-5-ancestors walk-up. Returns null when not found or the
249
+ * package.json has no string version.
250
+ */
251
+ function resolveInstalledRnVersion(appRoot /*: string */) /*: ?string */ {
252
+ let dir = path.resolve(appRoot);
253
+ for (let i = 0; i <= 5; i++) {
254
+ const pkgPath = path.join(
255
+ dir,
256
+ 'node_modules',
257
+ 'react-native',
258
+ 'package.json',
259
+ );
260
+ if (fs.existsSync(pkgPath)) {
261
+ try {
262
+ const j = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
263
+ if (typeof j.version === 'string') {
264
+ return j.version;
265
+ }
266
+ } catch {}
267
+ return null;
268
+ }
269
+ const parent = path.dirname(dir);
270
+ if (parent === dir) {
271
+ break;
272
+ }
273
+ dir = parent;
274
+ }
275
+ return null;
276
+ }
277
+
278
+ /**
279
+ * True when a version string can be resolved to a published remote tag. False
280
+ * for the monorepo dev placeholder ('1000.0.0') and 0.0.0-* dev builds, neither
281
+ * of which is published — in remote mode these require an explicit override.
282
+ */
283
+ function isPublishableVersion(v /*: ?string */) /*: boolean */ {
284
+ if (v == null || v === '') {
285
+ return false;
286
+ }
287
+ if (v === '1000.0.0') {
288
+ return false;
289
+ }
290
+ if (/^0\.0\.0(-|$)/.test(v)) {
291
+ return false;
292
+ }
293
+ return true;
294
+ }
295
+
296
+ /**
297
+ * Resolve the remote SPM package config for `appRoot`, or null for local mode.
298
+ *
299
+ * Remote mode is gated by a URL alone (env RN_SPM_REMOTE_URL or persisted
300
+ * `url`). The version is an OVERRIDE chain — env RN_SPM_REMOTE_VERSION →
301
+ * persisted `versionOverride` → legacy persisted `version` (back-compat) — and
302
+ * when no override is set it is DERIVED from the installed react-native. The
303
+ * derived value is never persisted, so an npm RN upgrade auto-re-pins the SPM
304
+ * graph on the next sync. A derived version that isn't publishable (or a
305
+ * missing RN install) throws RemoteVersionError so the dev placeholder doesn't
306
+ * silently pin an unpublished tag.
307
+ */
308
+ function remotePackageConfig(
309
+ appRoot /*: string */,
310
+ ) /*: ?{url: string, version: string, identity: string} */ {
311
+ const envUrl = process.env.RN_SPM_REMOTE_URL;
312
+ const envVersion = process.env.RN_SPM_REMOTE_VERSION;
313
+ const cfgPath = path.join(appRoot, REMOTE_CONFIG_REL);
314
+
315
+ // Read any persisted config first. Legacy {url, version} (where `version`
316
+ // was a hard pin) is read with `version` honored as an override.
317
+ let persisted /*: {url?: string, versionOverride?: string, version?: string} */ =
318
+ {};
319
+ if (fs.existsSync(cfgPath)) {
320
+ try {
321
+ const j = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
322
+ if (j != null && typeof j === 'object') {
323
+ persisted = j;
324
+ }
325
+ } catch {}
326
+ }
327
+
328
+ // URL alone activates remote mode (env wins over persisted).
329
+ const url = envUrl != null && envUrl !== '' ? envUrl : persisted.url;
330
+ if (url == null || url === '') {
331
+ return null; // local mode
332
+ }
333
+
334
+ // Version override chain. When none is set, derive from the installed RN —
335
+ // the SPM graph must compile against the same RN the JS/native code uses.
336
+ const override =
337
+ envVersion != null && envVersion !== ''
338
+ ? envVersion
339
+ : (persisted.versionOverride ?? persisted.version);
340
+ const version = override ?? resolveInstalledRnVersion(appRoot);
341
+
342
+ if (version == null) {
343
+ throw new RemoteVersionError(
344
+ `Remote SPM mode is on (URL=${url}) but no React Native version could ` +
345
+ 'be resolved (react-native was not found in node_modules). Set ' +
346
+ 'RN_SPM_REMOTE_VERSION to a published tag, or install react-native.',
347
+ );
348
+ }
349
+ if (override == null && !isPublishableVersion(version)) {
350
+ throw new RemoteVersionError(
351
+ `Remote SPM mode is on (URL=${url}) but React Native resolves to ` +
352
+ `non-publishable version '${version}'. Set RN_SPM_REMOTE_VERSION to a ` +
353
+ 'published tag, or install a released react-native.',
354
+ );
355
+ }
356
+
357
+ // Persist {url, versionOverride?} only when env-driven (matching the prior
358
+ // behavior of capturing env so Xcode-phase re-syncs keep the mode). A derived
359
+ // version is never frozen: omit versionOverride so the next run re-derives.
360
+ const envDriven =
361
+ (envUrl != null && envUrl !== '') ||
362
+ (envVersion != null && envVersion !== '');
363
+ if (envDriven) {
364
+ const toPersist /*: {url: string, versionOverride?: string} */ = {url};
365
+ if (override != null) {
366
+ toPersist.versionOverride = override;
367
+ }
368
+ fs.mkdirSync(path.dirname(cfgPath), {recursive: true});
369
+ fs.writeFileSync(cfgPath, JSON.stringify(toPersist, null, 2) + '\n');
370
+ }
371
+
372
+ return {
373
+ url,
374
+ version,
375
+ identity: remotePackageIdentity(url),
376
+ };
377
+ }
378
+ function perAppHeadersDir(appRoot /*: string */) /*: string */ {
379
+ return path.join(appRoot, PER_APP_HEADERS_REL);
380
+ }
381
+
382
+ /**
383
+ * Creates a first-wins symlink linker rooted at `outDir`. `linkInto` maps a
384
+ * virtual import path to a physical file; `foldDir` recursively links every
385
+ * .h/.hpp under a source root. `seen` records virtual->realpath so identical
386
+ * duplicates collapse to one inode and non-identical collisions are surfaced.
387
+ * Each header tree gets its OWN linker (own `seen` map) so first-wins does not
388
+ * span the shared/per-app boundary.
389
+ */
390
+ function createHeaderLinker(
391
+ outDir /*: string */,
392
+ logger /*: {log: (msg: string) => void} */,
393
+ ) /*: {
394
+ seen: Map<string, string>,
395
+ stats: {collisions: number},
396
+ linkInto: (virtualPath: string, physical: string) => void,
397
+ foldDir: (srcRoot: string) => void,
398
+ } */ {
399
+ const seen /*: Map<string, string> */ = new Map();
400
+ const stats = {collisions: 0};
401
+
402
+ function linkInto(
403
+ virtualPath /*: string */,
404
+ physical /*: string */,
405
+ ) /*: void */ {
406
+ let real;
407
+ try {
408
+ real = fs.realpathSync(physical);
409
+ } catch {
410
+ return; // physical file missing — skip
411
+ }
412
+ const prev = seen.get(virtualPath);
413
+ if (prev != null) {
414
+ if (prev !== real) {
415
+ stats.collisions++;
416
+ logger.log(
417
+ `WARNING: merged headers: non-identical collision for ${virtualPath} (kept first)`,
418
+ );
419
+ }
420
+ return;
421
+ }
422
+ seen.set(virtualPath, real);
423
+ const dest = path.join(outDir, virtualPath);
424
+ fs.mkdirSync(path.dirname(dest), {recursive: true});
425
+ fs.symlinkSync(real, dest);
426
+ }
427
+
428
+ function foldDir(srcRoot /*: string */) /*: void */ {
429
+ let real;
430
+ try {
431
+ real = fs.realpathSync(srcRoot);
432
+ } catch {
433
+ return;
434
+ }
435
+ if (!fs.statSync(real).isDirectory()) {
436
+ return;
437
+ }
438
+ const dirs /*: Array<string> */ = [real];
439
+ while (dirs.length > 0) {
440
+ const dir = dirs.pop();
441
+ if (dir == null) {
442
+ break;
443
+ }
444
+ for (const ent of fs.readdirSync(dir, {withFileTypes: true})) {
445
+ const name = String(ent.name);
446
+ const child = path.join(dir, name);
447
+ // statSync (not the Dirent flags) so symlinks are followed — the
448
+ // autolinking header farm is itself a symlink farm, so its leaf
449
+ // headers are symlinks, not regular files.
450
+ let st;
451
+ try {
452
+ st = fs.statSync(child);
453
+ } catch {
454
+ continue; // broken symlink — skip
455
+ }
456
+ if (st.isDirectory()) {
457
+ dirs.push(child);
458
+ } else if (
459
+ st.isFile() &&
460
+ (name.endsWith('.h') || name.endsWith('.hpp'))
461
+ ) {
462
+ linkInto(path.relative(real, child), child);
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ return {seen, stats, linkInto, foldDir};
469
+ }
470
+
471
+ /*::
472
+ type HeaderTreeResult = {path: ?string, virtualPaths: Set<string>};
473
+ */
474
+
475
+ /**
476
+ * Materializes the PER-APP header tree at
477
+ * <appRoot>/build/xcframeworks/ReactAppHeaders: autolinking dep headers + codegen
478
+ * output. Per-app because it depends on which libraries the app links and the
479
+ * app's generated specs. Returns {path, virtualPaths}.
480
+ */
481
+ function buildPerAppHeaderTree(
482
+ appRoot /*: string */,
483
+ logger /*: {log: (msg: string) => void} */ = {log() {}},
484
+ ) /*: HeaderTreeResult */ {
485
+ const outDir = perAppHeadersDir(appRoot);
486
+ // Build into a temp dir OUTSIDE every folded root, then swap into place. The
487
+ // farm's final home (outDir) is INSIDE build/generated/ios — a tree foldDir
488
+ // walks — so `outDir + '.tmp'` would ALSO sit there and foldDir would fold
489
+ // the half-built farm back into itself (a spurious `ReactAppHeaders.tmp/`
490
+ // namespace duplicating every codegen header). build/.react-app-headers.tmp
491
+ // is under build/ but not under any folded subdir.
492
+ const tmpDir = path.join(appRoot, 'build', '.react-app-headers.tmp');
493
+ fs.rmSync(tmpDir, {recursive: true, force: true});
494
+ fs.rmSync(outDir, {recursive: true, force: true});
495
+ fs.mkdirSync(tmpDir, {recursive: true});
496
+
497
+ const linker = createHeaderLinker(tmpDir, logger);
498
+ linker.foldDir(
499
+ path.join(appRoot, 'build', 'generated', 'autolinking', 'headers'),
500
+ );
501
+ linker.foldDir(path.join(appRoot, 'build', 'generated', 'ios'));
502
+ linker.foldDir(
503
+ path.join(appRoot, 'build', 'generated', 'ios', 'ReactCodegen'),
504
+ );
505
+
506
+ // Stub source so the farm is a valid SPM target (vended headers-only —
507
+ // see the ReactAppHeaders target in the codegen Package.swift template).
508
+ fs.writeFileSync(
509
+ path.join(tmpDir, 'ReactAppHeadersStub.c'),
510
+ '// ReactAppHeaders vends the per-app generated headers; this stub\n' +
511
+ '// satisfies SPM, which requires at least one source file per target.\n' +
512
+ 'static int ReactAppHeadersStub __attribute__((unused)) = 0;\n',
513
+ );
514
+ fs.mkdirSync(path.dirname(outDir), {recursive: true});
515
+ fs.renameSync(tmpDir, outDir);
516
+
517
+ logger.log(
518
+ `Built per-app header tree (${linker.seen.size} headers` +
519
+ (linker.stats.collisions > 0
520
+ ? `, ${linker.stats.collisions} non-identical collisions`
521
+ : '') +
522
+ ')',
523
+ );
524
+ return {path: outDir, virtualPaths: new Set(linker.seen.keys())};
525
+ }
526
+
527
+ /**
528
+ * Runs React Native codegen and installs the SPM Package.swift template
529
+ * into build/generated/ios/. Used by both setup-apple-spm.js and
530
+ * sync-spm-autolinking.js.
531
+ */
532
+
533
+ /**
534
+ * Installs the SPM codegen template into build/generated/ios/Package.swift.
535
+ * No-op when the template or the generated/ios dir is missing — codegen
536
+ * may not have produced output yet, or the project may be SPM-only.
537
+ *
538
+ * The template is copied verbatim in local mode: it holds only fixed-relative
539
+ * `.package(path:)` references (it lives at a known depth inside the app), and
540
+ * headers come from the ReactHeaders/ReactNativeHeaders targets + the
541
+ * ReactAppHeaders product — no loader, no absolute paths. In remote mode the
542
+ * ReactNative path-dep is rewritten to the `.package(url:exact:)` identity.
543
+ */
544
+ function installSpmCodegenTemplate(
545
+ appRoot /*: string */,
546
+ reactNativeRoot /*: string */,
547
+ logger /*: {log: (msg: string) => void} */ = {log() {}},
548
+ ) /*: void */ {
549
+ const spmTemplate = path.join(
550
+ reactNativeRoot,
551
+ 'scripts',
552
+ 'codegen',
553
+ 'templates',
554
+ 'Package.swift.spm-template',
555
+ );
556
+ const codegenPkgSwift = path.join(
557
+ appRoot,
558
+ 'build',
559
+ 'generated',
560
+ 'ios',
561
+ 'Package.swift',
562
+ );
563
+ if (!fs.existsSync(path.dirname(codegenPkgSwift))) {
564
+ // Codegen hasn't produced build/generated/ios yet — normal, nothing to do.
565
+ return;
566
+ }
567
+ if (!fs.existsSync(spmTemplate)) {
568
+ // Abnormal: the react-native package is missing its SPM codegen template.
569
+ // Without it, codegen's default mis-rooted Package.swift stays in place
570
+ // and every subsequent Resolve Package Graph fails — say so loudly.
571
+ logger.log(
572
+ `warning: SPM codegen template missing at ${spmTemplate} — ` +
573
+ 'build/generated/ios/Package.swift was NOT corrected and package ' +
574
+ 'resolution will likely fail. Is the react-native package complete?',
575
+ );
576
+ return;
577
+ }
578
+ let content = fs.readFileSync(spmTemplate, 'utf8');
579
+ // Remote mode: the codegen package depends on the remote ReactNative
580
+ // package identity instead of the local path-based artifacts package.
581
+ const remote = remotePackageConfig(appRoot);
582
+ if (remote != null) {
583
+ content = content
584
+ .replace(
585
+ '.package(name: "ReactNative", path: "../../xcframeworks"),',
586
+ `.package(url: "${remote.url}", exact: "${remote.version}"),`,
587
+ )
588
+ .split('package: "ReactNative")')
589
+ .join(`package: "${remote.identity}")`);
590
+ }
591
+ fs.writeFileSync(codegenPkgSwift, content, 'utf8');
592
+ logger.log(
593
+ 'Installed SPM codegen template' + (remote != null ? ' (remote mode)' : ''),
594
+ );
595
+ }
596
+
597
+ function runCodegenAndInstallTemplate(
598
+ projectRoot /*: string */,
599
+ appRoot /*: string */,
600
+ reactNativeRoot /*: string */,
601
+ logger /*: {log: (msg: string) => void} */ = {log() {}},
602
+ opts /*: {installTemplate?: boolean} */ = {},
603
+ ) /*: void */ {
604
+ const codegenScript = path.join(
605
+ reactNativeRoot,
606
+ 'scripts',
607
+ 'generate-codegen-artifacts.js',
608
+ );
609
+ if (!fs.existsSync(codegenScript)) {
610
+ return;
611
+ }
612
+ logger.log('Running codegen...');
613
+ const {execSync} = require('child_process');
614
+ const codegenArgs =
615
+ `node "${codegenScript}" -p "${projectRoot}" -t ios` +
616
+ (projectRoot !== appRoot ? ` -o "${appRoot}"` : '');
617
+ execSync(codegenArgs, {stdio: 'inherit', cwd: projectRoot});
618
+ // Callers that re-point the xcframework symlinks after codegen (e.g. the SPM
619
+ // sync, which runs generate-spm-package afterwards) install the template
620
+ // themselves once the symlinks are final; they pass installTemplate: false to
621
+ // avoid a wasted write that the later install would immediately supersede.
622
+ if (opts.installTemplate !== false) {
623
+ installSpmCodegenTemplate(appRoot, reactNativeRoot, logger);
624
+ }
625
+ }
626
+
627
+ module.exports = {
628
+ makeLogger,
629
+ displayPath,
630
+ sharedCacheDir,
631
+ defaultCacheDir,
632
+ toSwiftName,
633
+ deriveAppName,
634
+ readPackageJson,
635
+ findProjectRoot,
636
+ resolveReactNativeRoot,
637
+ buildPerAppHeaderTree,
638
+ remotePackageConfig,
639
+ resolveInstalledRnVersion,
640
+ isPublishableVersion,
641
+ RemoteVersionError,
642
+ installSpmCodegenTemplate,
643
+ runCodegenAndInstallTemplate,
644
+ SCAFFOLDER_MARKER,
645
+ };