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.
- package/Libraries/Core/InitializeCore.js +9 -1
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/ReactNative/AppRegistry.flow.js +1 -0
- package/Libraries/ReactNative/AppRegistryImpl.js +2 -3
- package/Libraries/ReactPrivate/ReactNativePrivateInterface.js +5 -116
- package/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow +5 -31
- package/React/Base/RCTVersion.m +1 -1
- package/React/I18n/RCTLocalizedString.mm +38 -2
- package/React-Core-prebuilt.podspec +45 -17
- package/React-Core.podspec +9 -2
- package/ReactAndroid/external-artifacts/build.gradle.kts +49 -0
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SynchronousMountItem.kt +8 -2
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/index.js +15 -3
- package/index.js.flow +0 -2
- package/package.json +27 -29
- package/react-native.config.js +81 -0
- package/scripts/cocoapods/fabric.rb +1 -1
- package/scripts/cocoapods/rncore.rb +35 -78
- package/scripts/cocoapods/rncore_facades.rb +232 -0
- package/scripts/cocoapods/rndependencies.rb +64 -3
- package/scripts/cocoapods/rndeps_facades.rb +193 -0
- package/scripts/cocoapods/spm.rb +78 -11
- package/scripts/codegen/templates/Package.swift.spm-template +97 -0
- package/scripts/react-native-xcode.sh +20 -0
- package/scripts/react_native_pods.rb +65 -16
- package/scripts/replace-rncore-version.js +53 -6
- package/scripts/setup-apple-spm.js +1165 -0
- package/scripts/spm/__doc__/rfc-spm-xcframework.md +707 -0
- package/scripts/spm/__doc__/spm-autolinking-plugins.md +244 -0
- package/scripts/spm/__doc__/spm-header-paths-contract.md +97 -0
- package/scripts/spm/__doc__/spm-plugins-assessment.md +128 -0
- package/scripts/spm/__doc__/spm-scripts.md +451 -0
- package/scripts/spm/autolinking-plugins.js +331 -0
- package/scripts/spm/download-spm-artifacts.js +1409 -0
- package/scripts/spm/expand-spm-dependencies.js +216 -0
- package/scripts/spm/flavored-frameworks.js +1008 -0
- package/scripts/spm/generate-spm-autolinking-config.js +161 -0
- package/scripts/spm/generate-spm-autolinking.js +1888 -0
- package/scripts/spm/generate-spm-package.js +302 -0
- package/scripts/spm/generate-spm-xcodeproj.js +2224 -0
- package/scripts/spm/read-podspec.js +695 -0
- package/scripts/spm/scaffold-package-swift.js +1206 -0
- package/scripts/spm/spm-pbxproj.js +654 -0
- package/scripts/spm/spm-types.js +517 -0
- package/scripts/spm/spm-utils.js +645 -0
- package/scripts/spm/sync-spm-autolinking.js +160 -0
- package/sdks/.hermesv1version +1 -0
- package/sdks/hermes-engine/utils/replace_hermes_version.js +18 -4
- package/sdks/hermes-engine/version.properties +1 -1
- package/src/asset-registry.js +1 -1
- package/src/react-private-interface.js +145 -0
- package/src/react-private-interface.js.flow +48 -0
- package/src/setup-env.js +22 -0
- package/src/unstable-internals-do-not-use.d.ts +214 -0
- package/src/unstable-internals-do-not-use.js +76 -0
- package/third-party-podspecs/ReactNativeDependencies.podspec +2 -2
- package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +2 -2
- package/types_generated/Libraries/ReactPrivate/ReactNativePrivateInterface.d.ts +6 -21
- package/types_generated/index.d.ts +1 -2
- package/types_generated/src/private/renderer/events/dispatchNativeEvent.d.ts +26 -0
- package/types_generated/src/react-private-interface.d.ts +33 -0
- package/Libraries/Utilities/SceneTracker.js +0 -42
- package/jest-preset.js +0 -26
- package/rn-get-polyfills.js +0 -13
- package/types/tsconfig.json +0 -17
- package/types_generated/Libraries/Components/Touchable/Touchable.d.ts +0 -261
- 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
|
+
};
|