react-native 0.87.0-rc.3 → 0.87.0
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/ReactNativeVersion.js +1 -1
- package/React/Base/RCTVersion.m +1 -1
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/package.json +10 -8
- package/scripts/cocoapods/rncore.rb +1 -0
- package/scripts/replace-rncore-version.js +51 -127
- package/scripts/setup-apple-spm.js +111 -9
- package/scripts/spm/__doc__/spm-scripts.md +36 -1
- package/scripts/spm/autolinking-plugins.js +2 -2
- package/scripts/spm/download-spm-artifacts.js +3 -3
- package/scripts/spm/generate-spm-autolinking-config.js +62 -2
- package/scripts/spm/generate-spm-autolinking.js +1 -1
- package/scripts/spm/generate-spm-xcodeproj.js +54 -12
- package/scripts/spm/scaffold-package-swift.js +3 -3
- package/scripts/spm/spm-types.js +3 -0
|
@@ -29,7 +29,7 @@ export default class ReactNativeVersion {
|
|
|
29
29
|
static major: number = 0;
|
|
30
30
|
static minor: number = 87;
|
|
31
31
|
static patch: number = 0;
|
|
32
|
-
static prerelease: string | null =
|
|
32
|
+
static prerelease: string | null = null;
|
|
33
33
|
|
|
34
34
|
static getVersionString(): string {
|
|
35
35
|
return `${this.major}.${this.minor}.${this.patch}${this.prerelease != null ? `-${this.prerelease}` : ''}`;
|
package/React/Base/RCTVersion.m
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native",
|
|
3
|
-
"version": "0.87.0
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "A framework for building native apps using React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
"types": null,
|
|
43
43
|
"default": "./Libraries/*.js"
|
|
44
44
|
},
|
|
45
|
-
"./scripts/*": "./scripts
|
|
45
|
+
"./scripts/*": "./scripts/*.js",
|
|
46
|
+
"./scripts/*.sh": "./scripts/*.sh",
|
|
47
|
+
"./scripts/*.rb": "./scripts/*.rb",
|
|
46
48
|
"./asset-registry": {
|
|
47
49
|
"types": null,
|
|
48
50
|
"default": "./src/asset-registry.js"
|
|
@@ -148,12 +150,12 @@
|
|
|
148
150
|
}
|
|
149
151
|
},
|
|
150
152
|
"dependencies": {
|
|
151
|
-
"@react-native/asset-utils": "0.87.0
|
|
152
|
-
"@react-native/codegen": "0.87.0
|
|
153
|
-
"@react-native/community-cli-plugin": "0.87.0
|
|
154
|
-
"@react-native/gradle-plugin": "0.87.0
|
|
155
|
-
"@react-native/normalize-colors": "0.87.0
|
|
156
|
-
"@react-native/virtualized-lists": "0.87.0
|
|
153
|
+
"@react-native/asset-utils": "0.87.0",
|
|
154
|
+
"@react-native/codegen": "0.87.0",
|
|
155
|
+
"@react-native/community-cli-plugin": "0.87.0",
|
|
156
|
+
"@react-native/gradle-plugin": "0.87.0",
|
|
157
|
+
"@react-native/normalize-colors": "0.87.0",
|
|
158
|
+
"@react-native/virtualized-lists": "0.87.0",
|
|
157
159
|
"anser": "^1.4.9",
|
|
158
160
|
"ansi-regex": "^5.0.0",
|
|
159
161
|
"babel-plugin-syntax-hermes-parser": "0.36.1",
|
|
@@ -568,6 +568,7 @@ class ReactNativeCoreUtils
|
|
|
568
568
|
# Quoted so a $(PODS_ROOT) containing spaces stays a single clang argument.
|
|
569
569
|
module_map_flag = " \"-fmodule-map-file=$(PODS_ROOT)/React-Core-prebuilt/Headers/module.modulemap\""
|
|
570
570
|
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", module_map_flag)
|
|
571
|
+
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CPLUSPLUSFLAGS", module_map_flag)
|
|
571
572
|
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc" + module_map_flag)
|
|
572
573
|
end
|
|
573
574
|
end
|
|
@@ -59,7 +59,7 @@ function replaceRNCoreConfiguration(
|
|
|
59
59
|
configuration /*: string */,
|
|
60
60
|
version /*: string */,
|
|
61
61
|
podsRoot /*: string */,
|
|
62
|
-
) {
|
|
62
|
+
) /*: void */ {
|
|
63
63
|
// Filename comes from rncore.rb
|
|
64
64
|
const tarballURLPath = `${podsRoot}/ReactNativeCore-artifacts/reactnative-core-${version.toLowerCase()}-${configuration.toLowerCase()}.tar.gz`;
|
|
65
65
|
|
|
@@ -73,18 +73,6 @@ function replaceRNCoreConfiguration(
|
|
|
73
73
|
const tmpExtractDir = path.join(tmpDir, 'React-Core-prebuilt');
|
|
74
74
|
fs.mkdirSync(tmpExtractDir, {recursive: true});
|
|
75
75
|
|
|
76
|
-
// Preserve Expo-generated modulemap before replacing directories
|
|
77
|
-
const useFrameworksModulemapName = 'React-use-frameworks.modulemap';
|
|
78
|
-
const useFrameworksModulemapPath = path.join(
|
|
79
|
-
finalLocation,
|
|
80
|
-
useFrameworksModulemapName,
|
|
81
|
-
);
|
|
82
|
-
let savedModulemap = null;
|
|
83
|
-
if (fs.existsSync(useFrameworksModulemapPath)) {
|
|
84
|
-
console.log('Preserving', useFrameworksModulemapName);
|
|
85
|
-
savedModulemap = fs.readFileSync(useFrameworksModulemapPath);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
76
|
try {
|
|
89
77
|
console.log('Extracting the tarball to temp dir', tarballURLPath);
|
|
90
78
|
const result = spawnSync(
|
|
@@ -110,98 +98,30 @@ function replaceRNCoreConfiguration(
|
|
|
110
98
|
);
|
|
111
99
|
}
|
|
112
100
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const src = path.join(tmpExtractDir, direntName);
|
|
134
|
-
const dst = path.join(finalLocation, direntName);
|
|
135
|
-
const mvResult = spawnSync('mv', [src, dst], {stdio: 'inherit'});
|
|
136
|
-
if (mvResult.status !== 0) {
|
|
137
|
-
// Fallback: copy recursively then remove source
|
|
138
|
-
console.log(`mv failed for ${direntName}, falling back to cp -R`);
|
|
139
|
-
const cpResult = spawnSync('cp', ['-R', src, dst], {
|
|
140
|
-
stdio: 'inherit',
|
|
141
|
-
});
|
|
142
|
-
if (cpResult.status !== 0) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
`cp fallback failed with exit code ${cpResult.status}`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
101
|
+
// Replace only the compiled framework. Headers/ is flattened from
|
|
102
|
+
// ReactNativeHeaders by the podspec prepare_command, and the prebuild
|
|
103
|
+
// compose job emits one set of those headers for both configurations, so a
|
|
104
|
+
// config switch leaves them identical. Leaving them alone keeps
|
|
105
|
+
// Headers/module.modulemap — which consumers activate through
|
|
106
|
+
// -fmodule-map-file — in place for the whole build; deleting and recreating
|
|
107
|
+
// it mid-build lets a concurrent dependency scan miss it, and the React
|
|
108
|
+
// module then precompiles without it (#57803).
|
|
109
|
+
const dest = path.join(finalLocation, 'React.xcframework');
|
|
110
|
+
console.log('Replacing', dest);
|
|
111
|
+
fs.rmSync(dest, {force: true, recursive: true});
|
|
112
|
+
const mvResult = spawnSync('mv', [xcfwPath, dest], {stdio: 'inherit'});
|
|
113
|
+
if (mvResult.status !== 0) {
|
|
114
|
+
// Fallback: copy recursively then remove source
|
|
115
|
+
console.log('mv failed for React.xcframework, falling back to cp -R');
|
|
116
|
+
const cpResult = spawnSync('cp', ['-R', xcfwPath, dest], {
|
|
117
|
+
stdio: 'inherit',
|
|
118
|
+
});
|
|
119
|
+
if (cpResult.status !== 0) {
|
|
120
|
+
throw new Error(`cp fallback failed with exit code ${cpResult.status}`);
|
|
147
121
|
}
|
|
148
122
|
}
|
|
149
|
-
|
|
150
|
-
// The podspec prepare_command flattens ReactNativeHeaders' headers into a
|
|
151
|
-
// top-level Headers/ dir, but it does not re-run on a config swap. Mirror
|
|
152
|
-
// it here: re-flatten the headers (identical across slices) and drop the
|
|
153
|
-
// now-redundant xcframework so $(PODS_ROOT)/React-Core-prebuilt/Headers
|
|
154
|
-
// keeps resolving <react/...>, <yoga/...>, etc.
|
|
155
|
-
//
|
|
156
|
-
// Fail closed when the swapped-in tarball lacks ReactNativeHeaders: the
|
|
157
|
-
// directory purge above already deleted the previous Headers/, so
|
|
158
|
-
// continuing silently would leave the injected -fmodule-map-file flag
|
|
159
|
-
// dangling and break every <react/...> include only on a config switch —
|
|
160
|
-
// with no pointer to the version-skewed artifact that caused it.
|
|
161
|
-
const rnhXcfw = path.join(finalLocation, 'ReactNativeHeaders.xcframework');
|
|
162
|
-
if (!fs.existsSync(rnhXcfw)) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
`ReactNativeHeaders.xcframework not found in the extracted tarball at ${finalLocation}. ` +
|
|
165
|
-
'The downloaded artifact predates the headers-spec layout (or is incomplete); ' +
|
|
166
|
-
'use a prebuilt tarball matching this react-native version.',
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
const slice = fs
|
|
170
|
-
.readdirSync(rnhXcfw, {withFileTypes: true})
|
|
171
|
-
.find(
|
|
172
|
-
dirent =>
|
|
173
|
-
dirent.isDirectory() &&
|
|
174
|
-
fs.existsSync(path.join(rnhXcfw, dirent.name.toString(), 'Headers')),
|
|
175
|
-
);
|
|
176
|
-
if (!slice) {
|
|
177
|
-
throw new Error(
|
|
178
|
-
`No slice with a Headers directory found inside ${rnhXcfw}.`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
const headersDest = path.join(finalLocation, 'Headers');
|
|
182
|
-
fs.rmSync(headersDest, {force: true, recursive: true});
|
|
183
|
-
const cpHeaders = spawnSync(
|
|
184
|
-
'cp',
|
|
185
|
-
['-R', path.join(rnhXcfw, slice.name.toString(), 'Headers'), headersDest],
|
|
186
|
-
{stdio: 'inherit'},
|
|
187
|
-
);
|
|
188
|
-
if (cpHeaders.status !== 0) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`Flattening ReactNativeHeaders failed with exit code ${cpHeaders.status}`,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
fs.rmSync(rnhXcfw, {force: true, recursive: true});
|
|
194
123
|
} finally {
|
|
195
|
-
// Clean up temp directory
|
|
196
124
|
fs.rmSync(tmpDir, {force: true, recursive: true});
|
|
197
|
-
|
|
198
|
-
// Restore Expo-generated modulemap after directory replacement.
|
|
199
|
-
// Runs in finally so it is not skipped if mv/cp partially fails.
|
|
200
|
-
if (savedModulemap != null) {
|
|
201
|
-
const restoredPath = path.join(finalLocation, useFrameworksModulemapName);
|
|
202
|
-
fs.writeFileSync(restoredPath, savedModulemap);
|
|
203
|
-
console.log('Restored', useFrameworksModulemapName);
|
|
204
|
-
}
|
|
205
125
|
}
|
|
206
126
|
}
|
|
207
127
|
|
|
@@ -227,29 +147,33 @@ function main(
|
|
|
227
147
|
console.log('Done replacing React Native prebuilt');
|
|
228
148
|
}
|
|
229
149
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
150
|
+
if (require.main === module) {
|
|
151
|
+
// This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok
|
|
152
|
+
const argv = yargs
|
|
153
|
+
.option('c', {
|
|
154
|
+
alias: 'configuration',
|
|
155
|
+
description:
|
|
156
|
+
'Configuration to use to download the right React-Core prebuilt version. Allowed values are "Debug" and "Release".',
|
|
157
|
+
})
|
|
158
|
+
.option('r', {
|
|
159
|
+
alias: 'reactNativeVersion',
|
|
160
|
+
description:
|
|
161
|
+
'The Version of React Native associated with the React-Core prebuilt tarball.',
|
|
162
|
+
})
|
|
163
|
+
.option('p', {
|
|
164
|
+
alias: 'podsRoot',
|
|
165
|
+
description: 'The path to the Pods root folder',
|
|
166
|
+
})
|
|
167
|
+
.usage('Usage: $0 -c Debug -r <version> -p <path/to/react-native>').argv;
|
|
168
|
+
|
|
169
|
+
// $FlowFixMe[prop-missing]
|
|
170
|
+
const configuration = argv.configuration;
|
|
171
|
+
// $FlowFixMe[prop-missing]
|
|
172
|
+
const version = argv.reactNativeVersion;
|
|
173
|
+
// $FlowFixMe[prop-missing]
|
|
174
|
+
const podsRoot = argv.podsRoot;
|
|
175
|
+
|
|
176
|
+
main(configuration, version, podsRoot);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {replaceRNCoreConfiguration};
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
* must contain debug/ and release/ cache slots.
|
|
56
56
|
* [advanced] --download <auto|skip|force> Artifact policy (default: auto).
|
|
57
57
|
* [advanced] --skip-codegen Skip the react-native codegen step.
|
|
58
|
+
* [advanced] --config-command <json> Override the autolinking config command.
|
|
58
59
|
*
|
|
59
60
|
* Steps performed (add/update):
|
|
60
61
|
* 1. react-native codegen → build/generated/ios/ + install SPM codegen template
|
|
@@ -83,14 +84,19 @@ const {
|
|
|
83
84
|
} = require('./spm/generate-spm-autolinking');
|
|
84
85
|
const {
|
|
85
86
|
generateAutolinkingConfig,
|
|
87
|
+
parseConfigCommandJson,
|
|
88
|
+
readEnvConfigCommand,
|
|
89
|
+
resolveEnvConfigCommand,
|
|
86
90
|
} = require('./spm/generate-spm-autolinking-config');
|
|
87
91
|
const {main: generatePackage} = require('./spm/generate-spm-package');
|
|
88
92
|
const {findSourcePath} = require('./spm/generate-spm-package');
|
|
89
93
|
const {
|
|
94
|
+
SPM_INJECTED_MARKER,
|
|
90
95
|
cleanupDanglingJavaScriptCoreRef,
|
|
91
96
|
cleanupLeftoverPodsGroup,
|
|
92
97
|
findInjectedXcodeproj,
|
|
93
98
|
injectSpmIntoExistingXcodeproj,
|
|
99
|
+
readPinnedConfigCommand,
|
|
94
100
|
removeSpmInjection,
|
|
95
101
|
} = require('./spm/generate-spm-xcodeproj');
|
|
96
102
|
const {scaffoldAll} = require('./spm/scaffold-package-swift');
|
|
@@ -187,6 +193,11 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
|
|
|
187
193
|
default: false,
|
|
188
194
|
describe: '[advanced] Skip the react-native codegen step',
|
|
189
195
|
})
|
|
196
|
+
.option('config-command', {
|
|
197
|
+
type: 'string',
|
|
198
|
+
describe:
|
|
199
|
+
'[advanced] JSON array of the argv used to generate autolinking.json, overriding the default @react-native-community/cli config command. Also settable via RCT_SPM_AUTOLINKING_CONFIG_COMMAND. Either way `add`/`update` remembers the value in .spm-injected.json, so later runs and Xcode builds reuse it. Example: \'["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]\'',
|
|
200
|
+
})
|
|
190
201
|
.usage(
|
|
191
202
|
'Usage: $0 [action] [options]\n\nSets up Swift Package Manager support in a React Native app.',
|
|
192
203
|
)
|
|
@@ -214,6 +225,10 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
|
|
|
214
225
|
version: parsed.version ?? null,
|
|
215
226
|
artifacts: parsed.artifacts ?? null,
|
|
216
227
|
skipCodegen: parsed['skip-codegen'],
|
|
228
|
+
configCommand:
|
|
229
|
+
parsed['config-command'] != null
|
|
230
|
+
? parseConfigCommandJson(parsed['config-command'], '--config-command')
|
|
231
|
+
: null,
|
|
217
232
|
downloadPolicy: parsed.download,
|
|
218
233
|
productName: parsed['product-name'] ?? null,
|
|
219
234
|
xcodeprojPath: parsed.xcodeproj ?? null,
|
|
@@ -833,6 +848,7 @@ async function setupXcodeproj(
|
|
|
833
848
|
// (injectSpmIntoExistingXcodeproj preserves it — see
|
|
834
849
|
// generate-spm-xcodeproj.js).
|
|
835
850
|
artifactsVersionOverride: args.version ?? null,
|
|
851
|
+
configCommand: resolveConfigCommandToPin(args),
|
|
836
852
|
});
|
|
837
853
|
if (result.status !== 'injected') {
|
|
838
854
|
logError(`SPM injection failed: ${result.reason}`);
|
|
@@ -892,6 +908,88 @@ function logNextSteps(
|
|
|
892
908
|
log('To remove SPM later: `npx react-native spm deinit`');
|
|
893
909
|
}
|
|
894
910
|
|
|
911
|
+
// The autolinking config command for this run: an explicit `--config-command`
|
|
912
|
+
// first, then the value a previous `add`/`update` pinned into the injection
|
|
913
|
+
// marker. undefined means "no explicit command", which is what makes
|
|
914
|
+
// generateAutolinkingConfig fall back to RCT_SPM_AUTOLINKING_CONFIG_COMMAND and
|
|
915
|
+
// then to the built-in default — so the pin has to be WITHHELD while the env
|
|
916
|
+
// var is set, or a stale pin would outrank a developer's env override.
|
|
917
|
+
function resolveExplicitConfigCommand(
|
|
918
|
+
args /*: SetupArgs */,
|
|
919
|
+
appRoot /*: string */,
|
|
920
|
+
) /*: Array<string> | void */ {
|
|
921
|
+
if (args.configCommand != null) {
|
|
922
|
+
return args.configCommand;
|
|
923
|
+
}
|
|
924
|
+
if (readEnvConfigCommand() != null) {
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
const pinned = readPinnedConfigCommand(appRoot);
|
|
928
|
+
if (pinned == null) {
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
log(
|
|
932
|
+
`Autolinking config command (pinned in ${SPM_INJECTED_MARKER}): ` +
|
|
933
|
+
pinned.join(' '),
|
|
934
|
+
);
|
|
935
|
+
return pinned;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// The command to record in the injection marker. The env var is resolved here
|
|
939
|
+
// too, because the Xcode build phase inherits neither the flag nor the shell
|
|
940
|
+
// that set it — an env-only override that went unpinned would leave the build
|
|
941
|
+
// re-deriving autolinking.json with the default command. null pins nothing and
|
|
942
|
+
// preserves any earlier pin. An invalid env value throws, as the flag does,
|
|
943
|
+
// though `add` has already failed closed on it by this point.
|
|
944
|
+
function resolveConfigCommandToPin(
|
|
945
|
+
args /*: SetupArgs */,
|
|
946
|
+
) /*: ?Array<string> */ {
|
|
947
|
+
return args.configCommand ?? resolveEnvConfigCommand();
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Generate autolinking.json, failing closed on a config-command error.
|
|
951
|
+
//
|
|
952
|
+
// generateAutolinkingConfig throws ONLY when the config command itself fails —
|
|
953
|
+
// a non-zero exit, unparseable output, or a config missing
|
|
954
|
+
// project.ios.sourceDir. Swallowing that (the old behavior) let the run proceed
|
|
955
|
+
// and emit an empty Autolinked package, which only surfaced much later as an
|
|
956
|
+
// inscrutable `unable to resolve module dependency` at build time. Instead we
|
|
957
|
+
// set process.exitCode = 2 (a hard Xcode build-phase error, matching the
|
|
958
|
+
// RemoteVersionError path) and return null so the caller stops.
|
|
959
|
+
//
|
|
960
|
+
// A genuinely native-module-free app does NOT reach the error path: its command
|
|
961
|
+
// exits 0 with valid, empty-dependency JSON, so generateAutolinkingConfig
|
|
962
|
+
// returns normally and the empty-package path downstream stays valid.
|
|
963
|
+
function generateAutolinkingConfigOrFailClosed(
|
|
964
|
+
opts /*: {
|
|
965
|
+
projectRoot: string,
|
|
966
|
+
configCommand?: Array<string>,
|
|
967
|
+
generate?: typeof generateAutolinkingConfig,
|
|
968
|
+
} */,
|
|
969
|
+
) /*: ?AutolinkingConfigResult */ {
|
|
970
|
+
const generate = opts.generate ?? generateAutolinkingConfig;
|
|
971
|
+
try {
|
|
972
|
+
return generate({
|
|
973
|
+
projectRoot: opts.projectRoot,
|
|
974
|
+
configCommand: opts.configCommand,
|
|
975
|
+
});
|
|
976
|
+
} catch (e) {
|
|
977
|
+
logError(
|
|
978
|
+
`Failed to generate autolinking.json: ${e.message}\n` +
|
|
979
|
+
'The autolinking config command failed. If this app replaces ' +
|
|
980
|
+
'@react-native-community/cli autolinking (e.g. an Expo app), set ' +
|
|
981
|
+
'RCT_SPM_AUTOLINKING_CONFIG_COMMAND (or pass --config-command) to a ' +
|
|
982
|
+
'JSON argv array whose command prints the React Native CLI config, ' +
|
|
983
|
+
'e.g. \'["npx","expo-modules-autolinking","react-native-config",' +
|
|
984
|
+
'"--json","--platform","ios"]\'. An earlier `add`/`update` may also ' +
|
|
985
|
+
`have pinned a command in ${SPM_INJECTED_MARKER}; re-run with ` +
|
|
986
|
+
'--config-command to replace a stale one.',
|
|
987
|
+
);
|
|
988
|
+
process.exitCode = 2;
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
895
993
|
async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
|
|
896
994
|
let appRoot = process.cwd();
|
|
897
995
|
const projectRoot = findProjectRoot(appRoot);
|
|
@@ -981,16 +1079,16 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
|
|
|
981
1079
|
let autolinkingConfigResult /*: ?AutolinkingConfigResult */ = null;
|
|
982
1080
|
if (needsCliConfig) {
|
|
983
1081
|
log('Generating autolinking.json (CLI config)...');
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
);
|
|
1082
|
+
autolinkingConfigResult = generateAutolinkingConfigOrFailClosed({
|
|
1083
|
+
projectRoot,
|
|
1084
|
+
configCommand: resolveExplicitConfigCommand(args, appRoot),
|
|
1085
|
+
});
|
|
1086
|
+
if (autolinkingConfigResult == null) {
|
|
1087
|
+
// Fail closed: the config command errored and the helper already set
|
|
1088
|
+
// process.exitCode = 2. Stop rather than emit an empty Autolinked package.
|
|
1089
|
+
return;
|
|
993
1090
|
}
|
|
1091
|
+
log(`Wrote ${path.relative(appRoot, autolinkingConfigResult.outputPath)}`);
|
|
994
1092
|
}
|
|
995
1093
|
const reactNativeRoot = resolveReactNativeRoot(
|
|
996
1094
|
autolinkingConfigResult,
|
|
@@ -1159,7 +1257,11 @@ module.exports = {
|
|
|
1159
1257
|
main,
|
|
1160
1258
|
detectStandardRnLayoutRedirect,
|
|
1161
1259
|
findInjectedXcodeproj,
|
|
1260
|
+
generateAutolinkingConfigOrFailClosed,
|
|
1261
|
+
parseArgs,
|
|
1162
1262
|
resolveAction,
|
|
1263
|
+
resolveConfigCommandToPin,
|
|
1264
|
+
resolveExplicitConfigCommand,
|
|
1163
1265
|
shouldAutoDeintegrate,
|
|
1164
1266
|
ensureBothArtifactFlavors,
|
|
1165
1267
|
};
|
|
@@ -137,6 +137,40 @@ accepts kebab-case equivalents (e.g. `--skip-codegen`).
|
|
|
137
137
|
| `--artifacts <path>` | [advanced] Local artifact root containing complete `debug/` and `release/` cache slots |
|
|
138
138
|
| `--download <auto\|skip\|force>` | [advanced] Artifact download policy (default: auto) |
|
|
139
139
|
| `--skipCodegen` | [advanced] Skip the codegen step |
|
|
140
|
+
| `--configCommand <json>` | [advanced] JSON array of the argv used to generate `autolinking.json`, overriding the default `@react-native-community/cli config` command. Also settable via the `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` env var. Either way the value is remembered, so you pass it once. Example: `'["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]'` |
|
|
141
|
+
|
|
142
|
+
### The autolinking config command is remembered
|
|
143
|
+
|
|
144
|
+
An app that replaces `@react-native-community/cli` autolinking (an Expo app,
|
|
145
|
+
for example) has to tell `spm` how to produce `autolinking.json`. Pass the
|
|
146
|
+
command once, on `add` or `update`:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx react-native spm add --configCommand '["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Every action that needs `autolinking.json` — `add`, `update`, `scaffold`, and
|
|
153
|
+
the build-time `sync` — resolves the command in this order:
|
|
154
|
+
|
|
155
|
+
1. `--configCommand`
|
|
156
|
+
2. `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`
|
|
157
|
+
3. the `configCommand` pinned in `MyApp.xcodeproj/.spm-injected.json` by an
|
|
158
|
+
earlier `add`/`update`
|
|
159
|
+
4. the default `@react-native-community/cli config`
|
|
160
|
+
|
|
161
|
+
`add`/`update` pin whichever of the first two routes supplied the command,
|
|
162
|
+
validated as an argv array; a later run that passes neither keeps the existing
|
|
163
|
+
pin, and passing `--configCommand` again replaces it. The pin exists because
|
|
164
|
+
the **Sync SPM Autolinking** build phase inherits neither your flag nor the
|
|
165
|
+
shell that exported the env var — without it, a successful `add` is followed by
|
|
166
|
+
failing builds, because the phase re-derives `autolinking.json` with the
|
|
167
|
+
default command. A pin never shadows the env var, so an override in your shell
|
|
168
|
+
still takes effect, and a pin that no longer parses is ignored in favor of the
|
|
169
|
+
default.
|
|
170
|
+
|
|
171
|
+
`deinit` deletes `.spm-injected.json`, and the pin with it. A later `add`
|
|
172
|
+
therefore falls back to the default command unless you pass `--configCommand`
|
|
173
|
+
(or export the env var) again.
|
|
140
174
|
|
|
141
175
|
### Debug/Release flavor is automatic
|
|
142
176
|
|
|
@@ -161,7 +195,7 @@ package graph, or require a second build.
|
|
|
161
195
|
| Path | Commit? | Why |
|
|
162
196
|
|------|---------|-----|
|
|
163
197
|
| `MyApp.xcodeproj/` | Yes | Your project, with SwiftPM injected in place. Holds your signing, capabilities, Build Phases — `add` only adds SwiftPM refs/settings, additively. |
|
|
164
|
-
| `MyApp.xcodeproj/.spm-injected.json` | Yes | Marker recording every edit `add` made, so `deinit` can surgically reverse it and re-runs stay idempotent. |
|
|
198
|
+
| `MyApp.xcodeproj/.spm-injected.json` | Yes | Marker recording every edit `add` made, so `deinit` can surgically reverse it and re-runs stay idempotent. Also pins settings later runs and Xcode builds must reuse, such as the [autolinking config command](#the-autolinking-config-command-is-remembered). |
|
|
165
199
|
| `build/generated/` | No | Codegen/autolinking output; regenerated |
|
|
166
200
|
| `build/xcframeworks/` | No | Symlinks to the machine-local artifact cache |
|
|
167
201
|
| `Package.resolved` | No | SwiftPM resolution file; machine-specific |
|
|
@@ -335,6 +369,7 @@ across apps; refresh it with `react-native spm update --download force`.
|
|
|
335
369
|
| "not contained in target" | Re-run setup (regenerates file-level symlinks) |
|
|
336
370
|
| Codegen fails | Use `--skipCodegen` to iterate on other parts |
|
|
337
371
|
| "SPM sync failed" warning | Check Xcode build log for details; node may not be in PATH — ensure `with-environment.sh` is present |
|
|
372
|
+
| "Sync SPM Autolinking" build phase fails: `'npx --no-install @react-native-community/cli config' exited with status 1` | This app replaces `@react-native-community/cli` autolinking (e.g. an Expo app). Re-run `spm add`/`update` with `--configCommand` (or with `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` exported) so the working command is pinned for the build phase to reuse — see [The autolinking config command is remembered](#the-autolinking-config-command-is-remembered). |
|
|
338
373
|
| Autolinking not updating on build | Touch `package.json` to force a sync, or delete `build/generated/autolinking/.spm-sync-stamp` |
|
|
339
374
|
| Stale SwiftPM state or corrupted build | `rm -rf build/ .build/`, then `react-native spm update`, then reopen Xcode |
|
|
340
375
|
| Want to revert to CocoaPods | `react-native spm deinit`, then `pod install` |
|
|
@@ -117,7 +117,7 @@ function discoverPlugins(
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
const pluginPath = path.resolve(dep.root, rel);
|
|
120
|
-
let fn
|
|
120
|
+
let fn /*: unknown */ = null;
|
|
121
121
|
try {
|
|
122
122
|
// $FlowFixMe[unsupported-syntax] dynamic require by computed path
|
|
123
123
|
fn = require(pluginPath);
|
|
@@ -175,7 +175,7 @@ function invokePlugins(
|
|
|
175
175
|
const seenFrameworkNames /*: Set<string> */ = new Set();
|
|
176
176
|
|
|
177
177
|
for (const {depName, pluginPath, plugin} of plugins) {
|
|
178
|
-
let result
|
|
178
|
+
let result /*: unknown */ = null;
|
|
179
179
|
try {
|
|
180
180
|
result = plugin(context);
|
|
181
181
|
} catch (e) {
|
|
@@ -943,7 +943,7 @@ async function processArtifact(
|
|
|
943
943
|
return localPath;
|
|
944
944
|
};
|
|
945
945
|
|
|
946
|
-
let tarPath
|
|
946
|
+
let tarPath /*: string */ = '';
|
|
947
947
|
let fromShared = false;
|
|
948
948
|
if (isLocalTarball) {
|
|
949
949
|
tarPath = url;
|
|
@@ -970,7 +970,7 @@ async function processArtifact(
|
|
|
970
970
|
onProgress(xcframeworkName, 0, 0, 0, false, 0);
|
|
971
971
|
}
|
|
972
972
|
const tmpExtractDir = path.join(outputDir, '.extract-tmp', label);
|
|
973
|
-
let xcfwPath
|
|
973
|
+
let xcfwPath /*: string */ = '';
|
|
974
974
|
try {
|
|
975
975
|
xcfwPath = extractXCFramework(tarPath, tmpExtractDir);
|
|
976
976
|
} catch (e) {
|
|
@@ -1349,7 +1349,7 @@ function validateArtifactsCache(
|
|
|
1349
1349
|
if (!fs.existsSync(artifactsJsonPath)) {
|
|
1350
1350
|
return `artifacts.json missing in ${artifactsDir}`;
|
|
1351
1351
|
}
|
|
1352
|
-
let json
|
|
1352
|
+
let json /*: {[string]: {xcframeworkPath: string, url: string}} */ = {};
|
|
1353
1353
|
try {
|
|
1354
1354
|
// $FlowFixMe[unclear-type] JSON.parse returns any
|
|
1355
1355
|
const parsed /*: any */ = JSON.parse(
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Invokes the React Native community CLI to produce its config and writes the
|
|
18
18
|
* raw JSON to <project.ios.sourceDir>/build/generated/autolinking/autolinking.json.
|
|
19
|
+
* The config command can be overridden by `--config-command` or
|
|
20
|
+
* `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`, in that order, before the default.
|
|
19
21
|
*
|
|
20
22
|
* No filtering or reshaping happens here — the downstream consumer
|
|
21
23
|
* (generate-spm-autolinking.js) does its own iOS-only filtering when reading
|
|
@@ -60,6 +62,32 @@ const FALLBACK_CONFIG_COMMAND = [
|
|
|
60
62
|
'config',
|
|
61
63
|
];
|
|
62
64
|
|
|
65
|
+
function parseConfigCommandJson(
|
|
66
|
+
raw /*: string */,
|
|
67
|
+
source /*: string */,
|
|
68
|
+
) /*: Array<string> */ {
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(raw);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`${source}: config command must be a JSON array of strings. Example: '["npx","@react-native-community/cli","config"]'`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
!Array.isArray(parsed) ||
|
|
80
|
+
parsed.length === 0 ||
|
|
81
|
+
!parsed.every(value => typeof value === 'string' && value.length > 0)
|
|
82
|
+
) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${source}: config command must be a non-empty JSON array of non-empty strings`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
|
|
63
91
|
function resolveDefaultConfigCommand(
|
|
64
92
|
projectRoot /*: string */,
|
|
65
93
|
) /*: Array<string> */ {
|
|
@@ -94,6 +122,31 @@ function resolveDefaultConfigCommand(
|
|
|
94
122
|
return FALLBACK_CONFIG_COMMAND;
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
const ENV_CONFIG_COMMAND = 'RCT_SPM_AUTOLINKING_CONFIG_COMMAND';
|
|
126
|
+
|
|
127
|
+
// The raw env override, or null when unset or blank. Exported so callers that
|
|
128
|
+
// only need to know whether the override is in play (setup-apple-spm.js) share
|
|
129
|
+
// this blankness rule instead of re-deriving it.
|
|
130
|
+
function readEnvConfigCommand() /*: ?string */ {
|
|
131
|
+
const raw = process.env[ENV_CONFIG_COMMAND];
|
|
132
|
+
return typeof raw === 'string' && raw.trim().length > 0 ? raw : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The env override, parsed and validated, or null when unset or blank. Throws
|
|
136
|
+
// on a set-but-invalid value — never silently degrades to the default.
|
|
137
|
+
function resolveEnvConfigCommand() /*: ?Array<string> */ {
|
|
138
|
+
const raw = readEnvConfigCommand();
|
|
139
|
+
return raw == null ? null : parseConfigCommandJson(raw, ENV_CONFIG_COMMAND);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Env-var / default resolution for the autolinking config command. An explicit
|
|
143
|
+
// `configCommand` (e.g. from `--config-command`) is handled upstream by
|
|
144
|
+
// generateAutolinkingConfig's destructuring default, so it never reaches here —
|
|
145
|
+
// this only decides between the env-var override and the built-in default.
|
|
146
|
+
function resolveConfigCommand(projectRoot /*: string */) /*: Array<string> */ {
|
|
147
|
+
return resolveEnvConfigCommand() ?? resolveDefaultConfigCommand(projectRoot);
|
|
148
|
+
}
|
|
149
|
+
|
|
97
150
|
function defaultCliRunner(
|
|
98
151
|
command /*: Array<string> */,
|
|
99
152
|
opts /*: {cwd: string} */,
|
|
@@ -116,7 +169,7 @@ function generateAutolinkingConfig(
|
|
|
116
169
|
) /*: GenerateAutolinkingConfigResult */ {
|
|
117
170
|
const {
|
|
118
171
|
projectRoot,
|
|
119
|
-
configCommand =
|
|
172
|
+
configCommand = resolveConfigCommand(projectRoot),
|
|
120
173
|
cliRunner = defaultCliRunner,
|
|
121
174
|
} = opts;
|
|
122
175
|
|
|
@@ -158,4 +211,11 @@ function generateAutolinkingConfig(
|
|
|
158
211
|
return {config, outputPath: outPath, rawJson};
|
|
159
212
|
}
|
|
160
213
|
|
|
161
|
-
module.exports = {
|
|
214
|
+
module.exports = {
|
|
215
|
+
generateAutolinkingConfig,
|
|
216
|
+
parseConfigCommandJson,
|
|
217
|
+
readEnvConfigCommand,
|
|
218
|
+
resolveConfigCommand,
|
|
219
|
+
resolveDefaultConfigCommand,
|
|
220
|
+
resolveEnvConfigCommand,
|
|
221
|
+
};
|
|
@@ -426,7 +426,7 @@ function hasMixedLanguageSources(absSource /*: string */) /*: boolean */ {
|
|
|
426
426
|
let hasClang = false;
|
|
427
427
|
const walk = (dir /*: string */, depth /*: number */) => {
|
|
428
428
|
if (depth > 6 || (hasSwift && hasClang)) return;
|
|
429
|
-
let entries
|
|
429
|
+
let entries /*: Array<{name: string, isDirectory(): boolean}> */ = [];
|
|
430
430
|
try {
|
|
431
431
|
// $FlowFixMe[incompatible-type] Dirent typing
|
|
432
432
|
entries = fs.readdirSync(dir, {withFileTypes: true});
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
const {readFlavoredFrameworksManifest} = require('./flavored-frameworks');
|
|
25
|
+
const {parseConfigCommandJson} = require('./generate-spm-autolinking-config');
|
|
25
26
|
const {
|
|
26
27
|
addArrayMembers,
|
|
27
28
|
addArrayStringValues,
|
|
@@ -1799,13 +1800,13 @@ function readGeneratedSourcesManifest(
|
|
|
1799
1800
|
appRoot /*: string */,
|
|
1800
1801
|
) /*: Array<GeneratedSource> */ {
|
|
1801
1802
|
const manifestPath = path.join(appRoot, SPM_GENERATED_SOURCES_MANIFEST);
|
|
1802
|
-
let raw
|
|
1803
|
+
let raw /*: string */ = '';
|
|
1803
1804
|
try {
|
|
1804
1805
|
raw = fs.readFileSync(manifestPath, 'utf8');
|
|
1805
1806
|
} catch {
|
|
1806
1807
|
return [];
|
|
1807
1808
|
}
|
|
1808
|
-
let entries
|
|
1809
|
+
let entries /*: unknown */ = null;
|
|
1809
1810
|
try {
|
|
1810
1811
|
entries = JSON.parse(raw);
|
|
1811
1812
|
} catch {
|
|
@@ -1844,7 +1845,7 @@ function readGeneratedSourcesManifest(
|
|
|
1844
1845
|
*/
|
|
1845
1846
|
function readMarker(
|
|
1846
1847
|
xcodeprojPath /*: string */,
|
|
1847
|
-
) /*: ?{generatedSources?: {[string]: Array<string>}, artifactsVersionOverride?: ?string, buildSettingChanges?: Array<BuildSettingChange>, ...} */ {
|
|
1848
|
+
) /*: ?{generatedSources?: {[string]: Array<string>}, artifactsVersionOverride?: ?string, configCommand?: ?Array<string>, buildSettingChanges?: Array<BuildSettingChange>, ...} */ {
|
|
1848
1849
|
const markerPath = path.join(xcodeprojPath, SPM_INJECTED_MARKER);
|
|
1849
1850
|
try {
|
|
1850
1851
|
// $FlowFixMe[incompatible-return] JSON.parse returns any
|
|
@@ -1857,10 +1858,11 @@ function readMarker(
|
|
|
1857
1858
|
// Returns the `*.xcodeproj` under `appRoot` carrying a `.spm-injected.json`
|
|
1858
1859
|
// marker (the user-owned project SPM packages were injected into in place),
|
|
1859
1860
|
// or null when none has been injected yet. Pure fs reads — safe for the
|
|
1860
|
-
//
|
|
1861
|
-
//
|
|
1861
|
+
// marker readers below, and for callers that only locate the project (setup-
|
|
1862
|
+
// apple-spm.js's action defaulting and `deinit`), to call without exercising
|
|
1863
|
+
// any pbxproj-editing machinery.
|
|
1862
1864
|
function findInjectedXcodeproj(appRoot /*: string */) /*: string | null */ {
|
|
1863
|
-
let entries
|
|
1865
|
+
let entries /*: Array<{name: string, isDirectory(): boolean}> */ = [];
|
|
1864
1866
|
try {
|
|
1865
1867
|
// $FlowFixMe[incompatible-type] Dirent typing
|
|
1866
1868
|
entries = fs.readdirSync(appRoot, {withFileTypes: true});
|
|
@@ -1884,11 +1886,13 @@ function findInjectedXcodeproj(appRoot /*: string */) /*: string | null */ {
|
|
|
1884
1886
|
* update --version` pinned into the injected xcodeproj's `.spm-injected.json`
|
|
1885
1887
|
* marker (see the field's doc comment in injectSpmIntoExistingXcodeproj
|
|
1886
1888
|
* below), or null when no project is injected yet, no override is pinned, or
|
|
1887
|
-
* the marker can't be read (never throws). Pure fs reads
|
|
1888
|
-
*
|
|
1889
|
-
* the
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
1889
|
+
* the marker can't be read (never throws). Pure fs reads.
|
|
1890
|
+
*
|
|
1891
|
+
* Nothing in production calls this yet: the pin is written but never read
|
|
1892
|
+
* back, so the build-time sync (sync-spm-autolinking.js) still derives the
|
|
1893
|
+
* version from node_modules/react-native/package.json and can heal against a
|
|
1894
|
+
* different artifact slot than the explicit `--version` selected. Only the
|
|
1895
|
+
* tests cover it.
|
|
1892
1896
|
*/
|
|
1893
1897
|
function readArtifactsVersionOverride(appRoot /*: string */) /*: ?string */ {
|
|
1894
1898
|
const xcodeprojPath = findInjectedXcodeproj(appRoot);
|
|
@@ -1899,13 +1903,39 @@ function readArtifactsVersionOverride(appRoot /*: string */) /*: ?string */ {
|
|
|
1899
1903
|
return typeof override === 'string' && override.length > 0 ? override : null;
|
|
1900
1904
|
}
|
|
1901
1905
|
|
|
1906
|
+
/**
|
|
1907
|
+
* Read the autolinking config command a previous `spm add`/`update` pinned into
|
|
1908
|
+
* the injected xcodeproj's `.spm-injected.json` marker, or null when nothing
|
|
1909
|
+
* usable is pinned. Pure fs reads, like readArtifactsVersionOverride above, but
|
|
1910
|
+
* this one IS wired: setup-apple-spm.js's resolveExplicitConfigCommand reads it
|
|
1911
|
+
* on add/update/scaffold and on the build-time `sync`. Re-validated through the
|
|
1912
|
+
* same parseConfigCommandJson the flag goes through, and never throws, so a
|
|
1913
|
+
* hand-edited marker degrades to the env-var/default command instead of
|
|
1914
|
+
* injecting a bogus argv into the build.
|
|
1915
|
+
*/
|
|
1916
|
+
function readPinnedConfigCommand(appRoot /*: string */) /*: ?Array<string> */ {
|
|
1917
|
+
const xcodeprojPath = findInjectedXcodeproj(appRoot);
|
|
1918
|
+
if (xcodeprojPath == null) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
const pinned = readMarker(xcodeprojPath)?.configCommand;
|
|
1922
|
+
if (pinned == null) {
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
try {
|
|
1926
|
+
return parseConfigCommandJson(JSON.stringify(pinned), SPM_INJECTED_MARKER);
|
|
1927
|
+
} catch {
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1902
1932
|
/**
|
|
1903
1933
|
* Add SPM packages to a user's EXISTING xcodeproj in place. Returns
|
|
1904
1934
|
* {status: 'injected', target} on success, or {status: 'refused', reason}
|
|
1905
1935
|
* when the project can't be safely edited (caller surfaces it; fail-loud).
|
|
1906
1936
|
*/
|
|
1907
1937
|
function injectSpmIntoExistingXcodeproj(
|
|
1908
|
-
opts /*: {appRoot: string, reactNativeRoot: string, xcodeprojPath: string, appName?: ?string, artifactsVersionOverride?: ?string} */,
|
|
1938
|
+
opts /*: {appRoot: string, reactNativeRoot: string, xcodeprojPath: string, appName?: ?string, artifactsVersionOverride?: ?string, configCommand?: ?Array<string>} */,
|
|
1909
1939
|
) /*: {status: 'injected', target: string} | {status: 'refused', reason: string} */ {
|
|
1910
1940
|
const {appRoot, reactNativeRoot, xcodeprojPath} = opts;
|
|
1911
1941
|
const pbxprojPath = path.join(xcodeprojPath, 'project.pbxproj');
|
|
@@ -2022,6 +2052,16 @@ function injectSpmIntoExistingXcodeproj(
|
|
|
2022
2052
|
prevMarker?.artifactsVersionOverride ??
|
|
2023
2053
|
null;
|
|
2024
2054
|
|
|
2055
|
+
// Same set-or-preserve contract as the version pin above, for the autolinking
|
|
2056
|
+
// config command `add`/`update` resolved (`--config-command` or
|
|
2057
|
+
// RCT_SPM_AUTOLINKING_CONFIG_COMMAND) — the build-time sync sees neither the
|
|
2058
|
+
// flag nor the developer's shell environment, so without the pin it
|
|
2059
|
+
// re-derives autolinking.json with the default @react-native-community/cli
|
|
2060
|
+
// command and breaks apps that replace it. Read back by
|
|
2061
|
+
// readPinnedConfigCommand (above). No "clear" verb yet either; `deinit` drops
|
|
2062
|
+
// the whole marker, this field with it.
|
|
2063
|
+
const configCommand = opts.configCommand ?? prevMarker?.configCommand ?? null;
|
|
2064
|
+
|
|
2025
2065
|
// Marker: idempotency signal + the exact, reversible record of every edit so
|
|
2026
2066
|
// `deinit` (removeSpmInjection) can undo precisely what was added.
|
|
2027
2067
|
writeIfChanged(
|
|
@@ -2038,6 +2078,7 @@ function injectSpmIntoExistingXcodeproj(
|
|
|
2038
2078
|
// `update` to reconcile away entries that left the manifest.
|
|
2039
2079
|
generatedSources: generatedSourceUuids,
|
|
2040
2080
|
artifactsVersionOverride,
|
|
2081
|
+
configCommand,
|
|
2041
2082
|
scheme: {
|
|
2042
2083
|
file: schemeResult.file,
|
|
2043
2084
|
created: schemeResult.status === 'created',
|
|
@@ -2220,5 +2261,6 @@ module.exports = {
|
|
|
2220
2261
|
removePreActionFromScheme,
|
|
2221
2262
|
findInjectedXcodeproj,
|
|
2222
2263
|
readArtifactsVersionOverride,
|
|
2264
|
+
readPinnedConfigCommand,
|
|
2223
2265
|
SPM_INJECTED_MARKER,
|
|
2224
2266
|
};
|
|
@@ -197,7 +197,7 @@ function collectSubdirs(
|
|
|
197
197
|
]);
|
|
198
198
|
const out /*: Array<string> */ = [];
|
|
199
199
|
const walk = (absDir /*: string */, relDir /*: string */) => {
|
|
200
|
-
let entries
|
|
200
|
+
let entries /*: Array<{name: string, isDirectory(): boolean}> */ = [];
|
|
201
201
|
try {
|
|
202
202
|
// $FlowFixMe[incompatible-type] Dirent typing
|
|
203
203
|
entries = fs.readdirSync(absDir, {withFileTypes: true});
|
|
@@ -930,7 +930,7 @@ function scaffoldPackageSwiftForDep(
|
|
|
930
930
|
};
|
|
931
931
|
}
|
|
932
932
|
|
|
933
|
-
let model
|
|
933
|
+
let model;
|
|
934
934
|
try {
|
|
935
935
|
model = readPodspec(podspecPath);
|
|
936
936
|
} catch (e) {
|
|
@@ -1126,7 +1126,7 @@ function scaffoldAll(
|
|
|
1126
1126
|
directDeps.push({name, root, platforms: {ios: iosPlatform}});
|
|
1127
1127
|
}
|
|
1128
1128
|
|
|
1129
|
-
let allDeps
|
|
1129
|
+
let allDeps /*: Array<AutolinkedDep> */ = [];
|
|
1130
1130
|
try {
|
|
1131
1131
|
allDeps = expandSpmDependencies(directDeps, {
|
|
1132
1132
|
readConfig: defaultReadConfig,
|
package/scripts/spm/spm-types.js
CHANGED
|
@@ -16,6 +16,9 @@ export type SetupArgs = {
|
|
|
16
16
|
// `debug/` and `release/` cache slots, each with artifacts.json.
|
|
17
17
|
artifacts: string | null,
|
|
18
18
|
skipCodegen: boolean,
|
|
19
|
+
// Overrides the autolinking config command; also settable via
|
|
20
|
+
// RCT_SPM_AUTOLINKING_CONFIG_COMMAND.
|
|
21
|
+
configCommand: Array<string> | null,
|
|
19
22
|
// Artifact download policy: 'auto' fetches when missing, 'skip' never
|
|
20
23
|
// fetches, 'force' clears the cache slot and re-downloads.
|
|
21
24
|
downloadPolicy: 'auto' | 'skip' | 'force',
|