react-native 0.88.0-rc.0 → 0.88.0-rc.1

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 (52) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/Libraries/Renderer/implementations/ReactFabric-dev.js +3056 -1531
  3. package/Libraries/Renderer/implementations/ReactFabric-prod.js +2239 -1011
  4. package/Libraries/Renderer/implementations/ReactFabric-profiling.js +2451 -1124
  5. package/React/Base/RCTVersion.m +1 -1
  6. package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +8 -0
  7. package/ReactAndroid/gradle.properties +1 -1
  8. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +7 -1
  9. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +11 -1
  10. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +3 -1
  11. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +3 -1
  12. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +12 -1
  13. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Canary_Android.kt +3 -1
  14. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +3 -1
  15. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  16. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +15 -1
  17. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +4 -1
  18. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  19. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +5 -1
  20. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +6 -1
  21. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +74 -56
  22. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +4 -2
  23. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +5 -1
  24. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +10 -1
  25. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h +5 -1
  26. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +2 -1
  27. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +6 -1
  28. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +3 -1
  29. package/package.json +10 -10
  30. package/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js +18 -4
  31. package/scripts/setup-apple-spm.js +82 -4
  32. package/scripts/spm/autolinking-plugins.js +8 -5
  33. package/scripts/spm/expand-spm-dependencies.js +217 -156
  34. package/scripts/spm/generate-spm-autolinking.js +114 -88
  35. package/scripts/spm/generate-spm-xcodeproj.js +67 -22
  36. package/scripts/spm/ios-deployment-target.js +378 -0
  37. package/scripts/spm/read-podspec.js +156 -8
  38. package/scripts/spm/scaffold-package-swift.js +272 -105
  39. package/scripts/spm/spm-pbxproj.js +36 -21
  40. package/scripts/spm/spm-types.js +25 -8
  41. package/scripts/spm/spm-utils.js +26 -0
  42. package/scripts/spm/swiftpm-config.js +245 -0
  43. package/scripts/spm/sync-spm-autolinking.js +11 -2
  44. package/sdks/hermes-engine/version.properties +1 -1
  45. package/src/private/featureflags/ReactNativeFeatureFlags.js +10 -5
  46. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -1
  47. package/src/private/webapis/dom/nodes/ReactNativeElement.js +6 -4
  48. package/src/private/webapis/dom/nodes/ReadOnlyText.js +6 -4
  49. package/src/react-private-interface.js +4 -0
  50. package/src/react-private-interface.js.flow +2 -0
  51. package/types_generated/Libraries/ReactNative/FabricUIManager.d.ts +47 -0
  52. package/types_generated/src/react-private-interface.d.ts +4 -1
@@ -38,11 +38,14 @@ import type {
38
38
  const {
39
39
  SpmNameCollisionError,
40
40
  defaultReadConfig,
41
+ defaultReadPodspec,
41
42
  defaultResolveDep,
42
43
  expandSpmDependencies,
44
+ resolveSwiftName,
43
45
  } = require('./expand-spm-dependencies');
44
46
  const {expandSpmSourceGlobs} = require('./generate-spm-autolinking');
45
- const {readPodspec} = require('./read-podspec');
47
+ const {MIN_IOS_VERSION_SUPPORTED} = require('./ios-deployment-target');
48
+ const {findPodspecs, readPodspecCached} = require('./read-podspec');
46
49
  const {
47
50
  REACT_CODEGEN_PACKAGE_NAME,
48
51
  REACT_CODEGEN_PRODUCTS,
@@ -51,12 +54,12 @@ const {
51
54
  SCAFFOLDER_MARKER,
52
55
  makeLogger,
53
56
  remotePackageConfig,
54
- toSwiftName,
55
57
  } = require('./spm-utils');
58
+ const {readSwiftpmConfig, writeSwiftpmName} = require('./swiftpm-config');
56
59
  const fs = require('node:fs');
57
60
  const path = require('node:path');
58
61
 
59
- const {log} = makeLogger('scaffold-package-swift');
62
+ const {log, warn} = makeLogger('scaffold-package-swift');
60
63
 
61
64
  // SCAFFOLDER_MARKER lives in spm-utils.js (shared, no import cycle). It must NOT
62
65
  // contain the autolinker's AUTOGEN_MARKER ('// AUTO-GENERATED by
@@ -105,7 +108,9 @@ const {log} = makeLogger('scaffold-package-swift');
105
108
  // v19: scaffolded C++ targets carry DEBUG/NDEBUG config defines so their Fabric
106
109
  // ABI matches the prebuilt React.framework (Release strips DebugStringConvertible
107
110
  // under NDEBUG). Bumped so existing scaffolds regenerate with the defines.
108
- const SCAFFOLDER_VERSION = 19;
111
+ // v20: the platform floor is the app's iOS deployment target in string form
112
+ // (the `.v15` enum cannot express a dependency minimum like 16.4).
113
+ const SCAFFOLDER_VERSION = 20;
109
114
  const SCAFFOLDER_VERSION_LINE_RE = /^\/\/ AUTO-SCAFFOLDED-VERSION: (\d+)$/m;
110
115
 
111
116
  const AUTOGEN_MARKER =
@@ -246,22 +251,16 @@ function translatePodspecToSpmTarget(
246
251
  ) /*: SpmScaffoldSpec */ {
247
252
  const warnings = [...model.warnings];
248
253
 
249
- // Swift target name: whatever the autolinker resolved for this dep — its
250
- // `spm.name` override when set, else toSwiftName(npm-name). The autolinker
251
- // registers the dep under that name in its aggregator (and in any sibling
252
- // spm.dependencies refs), so the scaffolded Package.swift's product/library
253
- // name must match it exactly — otherwise SPM resolution fails with a name
254
- // mismatch on `.product(name: "X", package: "X")`.
255
- //
256
- // The podspec's `header_dir` is captured separately: when it changes the
257
- // include surface (e.g. `<reanimated/...>` instead of `<ReactNativeReanimated/...>`),
258
- // header resolution already works via the -I flags from headerSearchPaths
259
- // (path-style includes like safe-area-context's
260
- // `<react/renderer/components/safeareacontext/...>` resolve through
261
- // `-I common/cpp/`). Module-style includes that NEED the target name to
262
- // match (e.g. reanimated's `<reanimated/X.h>` via SwiftPM's auto-generated
263
- // module map) are what `spm.name` is for.
264
- const swiftName = dep.swiftName ?? toSwiftName(dep.name);
254
+ // Whatever the autolinker resolved (see resolveSwiftName). It registers the
255
+ // dep under that name in its aggregator and in any sibling spm.dependencies
256
+ // refs, so the scaffolded product/library name must match it exactly —
257
+ // otherwise SPM fails to resolve `.product(name: "X", package: "X")`.
258
+ const swiftName = dep.swiftName;
259
+ if (swiftName == null) {
260
+ throw new Error(
261
+ `react-native spm scaffold: no resolved Swift name for '${dep.name}'. expandSpmDependencies must resolve every dep's name before a manifest is written.`,
262
+ );
263
+ }
265
264
 
266
265
  // Header search paths — substitute Xcode build-setting tokens against the
267
266
  // dep root. Anything we can't substitute is dropped + warned (avoids
@@ -566,6 +565,7 @@ type EmitContext = {
566
565
  // Relative path to the app's local xcframeworks package
567
566
  // (<appRoot>/build/xcframeworks). Only referenced when remote == null.
568
567
  localXcfwPackageDir?: ?string,
568
+ iosDeploymentTarget?: ?string,
569
569
  };
570
570
  */
571
571
 
@@ -582,6 +582,8 @@ function emitScaffoldedPackageSwift(
582
582
  ) /*: string */ {
583
583
  const slotComment =
584
584
  ctx.cacheSlotLabel != null ? `\n// Cache slot: ${ctx.cacheSlotLabel}` : '';
585
+ const iosDeploymentTarget /*: string */ =
586
+ ctx.iosDeploymentTarget ?? MIN_IOS_VERSION_SUPPORTED;
585
587
 
586
588
  // React headers need NO search paths — they come from the React /
587
589
  // ReactNativeHeaders binaryTargets and the ReactAppHeaders product (see
@@ -658,8 +660,7 @@ function emitScaffoldedPackageSwift(
658
660
 
659
661
  // Dependencies block. Always declares ReactNative if any React-core dep
660
662
  // was in the podspec; sibling RN deps come from autolinking's existing
661
- // .package(path: ...) graph — we reference them by toSwiftName(npmName)
662
- // since that's what the autolinker registers them under.
663
+ // .package(path: ...) graph, under the names the autolinker resolved.
663
664
  const packageDeps /*: Array<string> */ = [];
664
665
  const targetDeps /*: Array<string> */ = [];
665
666
  if (spec.coreReactNative) {
@@ -704,8 +705,12 @@ function emitScaffoldedPackageSwift(
704
705
  }
705
706
  }
706
707
  for (const siblingName of spec.siblingNames) {
707
- const swiftSibling =
708
- spec.siblingSwiftNames?.[siblingName] ?? toSwiftName(siblingName);
708
+ const swiftSibling = spec.siblingSwiftNames?.[siblingName];
709
+ if (swiftSibling == null) {
710
+ throw new Error(
711
+ `react-native spm scaffold: no resolved Swift name for the sibling '${siblingName}'. expandSpmDependencies must resolve every dep's name before a manifest is written.`,
712
+ );
713
+ }
709
714
  // The autolinker references each self-managed (scaffolded) dep through a
710
715
  // `libs/<SwiftName>` symlink, and SPM resolves a manifest's relative
711
716
  // package paths against that symlink location — so a sibling lives at
@@ -766,7 +771,7 @@ import PackageDescription
766
771
 
767
772
  let package = Package(
768
773
  name: "${spec.swiftName}",
769
- platforms: [.iOS(.v15)],
774
+ platforms: [.iOS("${iosDeploymentTarget}")],
770
775
  products: [
771
776
  .library(name: "${spec.swiftName}", targets: ["${spec.swiftName}"]),
772
777
  ],
@@ -784,6 +789,61 @@ ${packageDepsBlock} targets: [
784
789
  `;
785
790
  }
786
791
 
792
+ const PODSPEC_KEY_ORIGINS = {
793
+ header_dir: "its podspec's 'header_dir'",
794
+ module_name: "its podspec's 'module_name'",
795
+ name: 'its pod name',
796
+ };
797
+
798
+ // Null when the caller resolved the name without recording where it came from,
799
+ // since the alternative is inventing an origin.
800
+ function swiftNameOrigin(dep /*: AutolinkedDep */) /*: ?string */ {
801
+ switch (dep.swiftNameSource) {
802
+ case 'config':
803
+ return "its declared 'swiftpmConfig.name'";
804
+ case 'podspec':
805
+ return PODSPEC_KEY_ORIGINS[dep.swiftNamePodspecKey ?? 'name'];
806
+ case 'npm':
807
+ return 'its npm package name';
808
+ default:
809
+ return null;
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Says which SwiftPM name the library got and where that name came from. The
815
+ * name is the prefix every `#import <Name/Header.h>` against the library has to
816
+ * spell, and a podspec-derived one is written into the library's package.json,
817
+ * so it is not a decision to make silently.
818
+ */
819
+ function reportSwiftName(
820
+ dep /*: AutolinkedDep */,
821
+ swiftName /*: string */,
822
+ model /*: PodspecModel */,
823
+ ) /*: void */ {
824
+ const origin = swiftNameOrigin(dep);
825
+ if (origin == null) {
826
+ return;
827
+ }
828
+ log(`${dep.name}: SwiftPM name '${swiftName}' (from ${origin}).`);
829
+ // Two declared namespaces, one name: CocoaPods compiles the module as
830
+ // `module_name` while consumers import through `header_dir`, so a library
831
+ // that declares both loses one of them here.
832
+ const {headerDir, moduleName} = model;
833
+ if (
834
+ dep.swiftNamePodspecKey === 'header_dir' &&
835
+ headerDir != null &&
836
+ moduleName != null &&
837
+ headerDir !== moduleName
838
+ ) {
839
+ warn(
840
+ `${dep.name}'s podspec declares both a 'header_dir' ('${headerDir}') and a 'module_name' ('${moduleName}'): ` +
841
+ `'${swiftName}' is the SwiftPM name, and '${moduleName}' is not used. ` +
842
+ `Set 'swiftpmConfig.name' in its package.json to override that choice.`,
843
+ );
844
+ }
845
+ }
846
+
787
847
  // ---------------------------------------------------------------------------
788
848
  // Per-dep orchestrator
789
849
  // ---------------------------------------------------------------------------
@@ -806,9 +866,10 @@ type ScaffoldContext = {
806
866
  // `s.dependency` names (e.g. "RNWorklets") wire to the right sibling.
807
867
  podToNpm?: Map<string, string>,
808
868
  // npm-name → resolved Swift name over all autolinked deps, so sibling
809
- // references honor each sibling's `spm.name`.
869
+ // references honor each sibling's declared name.
810
870
  swiftNameByNpm?: Map<string, string>,
811
871
  remote: ?{url: string, version: string, identity: string},
872
+ iosDeploymentTarget?: ?string,
812
873
  };
813
874
  */
814
875
 
@@ -839,14 +900,14 @@ function scaffoldPackageSwiftForDep(
839
900
  };
840
901
  }
841
902
 
842
- // Dep can opt out via its own react-native.config.js.
843
- const cfg = defaultReadConfig(dep.root);
844
- // $FlowFixMe[prop-missing] config has dynamic shape
845
- if (cfg != null && cfg.spm != null && cfg.spm.scaffold === false) {
903
+ // Dep can opt out in its own package.json.
904
+ if (
905
+ readSwiftpmConfig(dep.root, defaultReadConfig(dep.root))?.scaffold === false
906
+ ) {
846
907
  return {
847
908
  depName,
848
909
  status: 'skipped-opt-out',
849
- reason: 'react-native.config.js sets spm.scaffold = false.',
910
+ reason: "the library sets 'swiftpmConfig.scaffold' to false.",
850
911
  };
851
912
  }
852
913
 
@@ -925,25 +986,10 @@ function scaffoldPackageSwiftForDep(
925
986
  }
926
987
  }
927
988
 
928
- // Find a podspec to read. autolinking.json may have provided podspecPath;
929
- // otherwise glob for *.podspec in dep root.
989
+ // autolinking.json may have provided podspecPath; otherwise search dep.root.
930
990
  // $FlowFixMe[prop-missing] dynamic shape from autolinking.json
931
- let podspecPath /*: ?string */ = dep.platforms.ios.podspecPath ?? null;
932
- if (podspecPath == null) {
933
- try {
934
- const entries = fs.readdirSync(dep.root);
935
- // Skip a crashed run's leftover patched copy (read-podspec.js stages it
936
- // as `.spm-scaffold-<pid>-<name>.podspec` next to the original).
937
- const candidate = entries.find(
938
- e => e.endsWith('.podspec') && !e.startsWith('.spm-scaffold-'),
939
- );
940
- if (candidate != null) {
941
- podspecPath = path.join(dep.root, candidate);
942
- }
943
- } catch {
944
- // dep.root may not exist; treat as no-podspec
945
- }
946
- }
991
+ const podspecPath /*: ?string */ =
992
+ dep.platforms.ios.podspecPath ?? findPodspecs(dep.root)[0];
947
993
  if (podspecPath == null || !fs.existsSync(podspecPath)) {
948
994
  return {
949
995
  depName,
@@ -954,7 +1000,7 @@ function scaffoldPackageSwiftForDep(
954
1000
 
955
1001
  let model;
956
1002
  try {
957
- model = readPodspec(podspecPath);
1003
+ model = readPodspecCached(podspecPath);
958
1004
  } catch (e) {
959
1005
  return {
960
1006
  depName,
@@ -1023,6 +1069,7 @@ function scaffoldPackageSwiftForDep(
1023
1069
  const content = emitScaffoldedPackageSwift(spec, {
1024
1070
  cacheSlotLabel: ctx.cacheSlotLabel,
1025
1071
  remote: ctx.remote,
1072
+ iosDeploymentTarget: ctx.iosDeploymentTarget,
1026
1073
  codegenPackageDir: relFromManifest('build', 'generated', 'ios'),
1027
1074
  localXcfwPackageDir: relFromManifest('build', 'xcframeworks'),
1028
1075
  });
@@ -1064,15 +1111,171 @@ function scaffoldPackageSwiftForDep(
1064
1111
  }
1065
1112
  }
1066
1113
 
1114
+ reportSwiftName(dep, spec.swiftName, model);
1115
+
1116
+ // Migration: record a name we derived from the podspec, so the library can
1117
+ // drop the podspec. A declared name is already where it belongs, and a name
1118
+ // guessed from the npm name would freeze a guess into someone's config.
1119
+ const swiftpmName =
1120
+ dep.swiftNameSource === 'podspec'
1121
+ ? writeSwiftpmName(dep.root, spec.swiftName, {dryRun: ctx.dryRun})
1122
+ : undefined;
1123
+
1067
1124
  return {
1068
1125
  depName,
1069
1126
  status: 'written',
1070
1127
  packageSwiftPath: pkgSwiftPath,
1071
1128
  warnings: spec.warnings,
1072
1129
  previouslyExisted,
1130
+ swiftpmName,
1073
1131
  };
1074
1132
  }
1075
1133
 
1134
+ /**
1135
+ * The dependency set the autolinker considers: the iOS entries of
1136
+ * autolinking.json plus their transitive SwiftPM dependencies. Null when the
1137
+ * file declares none; throws when it cannot be read or parsed. `onSkipped`
1138
+ * reports the entries dropped for having no iOS platform.
1139
+ */
1140
+ function collectAutolinkedDeps(
1141
+ opts /*: {
1142
+ autolinkingJsonPath: string,
1143
+ remote: ?{url: string, version: string, identity: string},
1144
+ onSkipped?: (name: string) => void,
1145
+ } */,
1146
+ ) /*: ?Array<AutolinkedDep> */ {
1147
+ const {autolinkingJsonPath, remote, onSkipped} = opts;
1148
+ /*:: type AutolinkingJson = {dependencies?: ?{[string]: {root?: string, platforms?: {ios?: ?{...}, ...}, ...}}, ...}; */
1149
+ // $FlowFixMe[incompatible-type] JSON.parse returns any
1150
+ const data /*: AutolinkingJson */ = JSON.parse(
1151
+ fs.readFileSync(autolinkingJsonPath, 'utf8'),
1152
+ );
1153
+ const deps = data.dependencies;
1154
+ if (deps == null) {
1155
+ return null;
1156
+ }
1157
+
1158
+ const directDeps /*: Array<AutolinkedDep> */ = [];
1159
+ for (const name of Object.keys(deps)) {
1160
+ const raw = deps[name];
1161
+ if (raw == null) continue;
1162
+ const root = raw.root;
1163
+ const ios = raw.platforms?.ios;
1164
+ if (typeof root !== 'string' || ios == null) {
1165
+ onSkipped?.(name);
1166
+ continue;
1167
+ }
1168
+ // $FlowFixMe[incompatible-type] `ios` shape is runtime-validated above
1169
+ const iosPlatform /*: AutolinkingIosPlatform */ = ios;
1170
+ directDeps.push({name, root, platforms: {ios: iosPlatform}});
1171
+ }
1172
+
1173
+ try {
1174
+ return expandSpmDependencies(directDeps, {
1175
+ readConfig: defaultReadConfig,
1176
+ resolveDep: defaultResolveDep,
1177
+ readPodspec: defaultReadPodspec,
1178
+ extraReservedNames: remote != null ? [remote.identity] : undefined,
1179
+ });
1180
+ } catch (e) {
1181
+ if (e instanceof SpmNameCollisionError) {
1182
+ throw e;
1183
+ }
1184
+ // A transitive-resolution failure shouldn't abort the whole pass; fall back
1185
+ // to the direct deps so at least those are covered. They are named the way
1186
+ // the autolinker names them — a manifest written under any other name
1187
+ // outlives this error on disk and then fails to resolve.
1188
+ log(`Transitive dependency expansion failed: ${e.message}`);
1189
+ return directDeps.map(dep => {
1190
+ const resolved = resolveSwiftName(
1191
+ dep.name,
1192
+ readSwiftpmConfig(dep.root, defaultReadConfig(dep.root)),
1193
+ defaultReadPodspec(dep.root, dep.platforms.ios.podspecPath),
1194
+ );
1195
+ return {
1196
+ ...dep,
1197
+ swiftName: resolved.name,
1198
+ swiftNameSource: resolved.source,
1199
+ swiftNamePodspecKey: resolved.podspecKey,
1200
+ };
1201
+ });
1202
+ }
1203
+ }
1204
+
1205
+ // The `.iOS(...)` element of the emitted platforms array — the `.v15` enum form
1206
+ // pre-v20 scaffolds carry included, and without the array's closing bracket so
1207
+ // a user-extended array (`[.iOS(…), .macOS(…)]`) is still matched.
1208
+ const PLATFORM_FLOOR_RE = /platforms: \[\.iOS\((\.v\d+|"[^"]*")\)/;
1209
+
1210
+ /**
1211
+ * Bring the platform floor of already-scaffolded manifests up to the app's
1212
+ * deployment target — `spm add`/`update` must not leave a dep pinned below the
1213
+ * app (SwiftPM would refuse to link it), and re-scaffolding is the user's call.
1214
+ * Only the platform line of a file carrying our own marker is rewritten; no
1215
+ * file is created. `from` is the previous token, verbatim (`15.1` or `.v15`).
1216
+ */
1217
+ function refreshScaffoldedPlatformFloors(
1218
+ opts /*: {appRoot: string, autolinkingJsonPath?: string, iosDeploymentTarget: string} */,
1219
+ ) /*: Array<{depName: string, path: string, from: string, to: string}> */ {
1220
+ const {appRoot, iosDeploymentTarget} = opts;
1221
+ const autolinkingJsonPath =
1222
+ opts.autolinkingJsonPath ??
1223
+ path.join(appRoot, 'build', 'generated', 'autolinking', 'autolinking.json');
1224
+ const refreshed = [];
1225
+
1226
+ // Best-effort: an unreadable autolinking.json, a name collision or a
1227
+ // malformed remote config is the autolinker's error to report a moment
1228
+ // later, not this pass's.
1229
+ let deps /*: ?Array<AutolinkedDep> */ = null;
1230
+ try {
1231
+ deps = collectAutolinkedDeps({
1232
+ autolinkingJsonPath,
1233
+ remote: remotePackageConfig(appRoot),
1234
+ });
1235
+ } catch {
1236
+ return refreshed;
1237
+ }
1238
+ if (deps == null) {
1239
+ return refreshed;
1240
+ }
1241
+
1242
+ for (const {name: depName, root} of deps) {
1243
+ const manifestPath = path.join(root, 'Package.swift');
1244
+ let content;
1245
+ try {
1246
+ content = fs.readFileSync(manifestPath, 'utf8');
1247
+ } catch {
1248
+ continue;
1249
+ }
1250
+ if (
1251
+ !content.includes(SCAFFOLDER_MARKER) ||
1252
+ content.includes(AUTOGEN_MARKER)
1253
+ ) {
1254
+ continue;
1255
+ }
1256
+ const match = content.match(PLATFORM_FLOOR_RE);
1257
+ if (match == null) {
1258
+ continue;
1259
+ }
1260
+ const from = match[1].replace(/"/g, '');
1261
+ if (from === iosDeploymentTarget) {
1262
+ continue;
1263
+ }
1264
+ fs.writeFileSync(
1265
+ manifestPath,
1266
+ content.replace(match[0], `platforms: [.iOS("${iosDeploymentTarget}")`),
1267
+ 'utf8',
1268
+ );
1269
+ refreshed.push({
1270
+ depName,
1271
+ path: manifestPath,
1272
+ from,
1273
+ to: iosDeploymentTarget,
1274
+ });
1275
+ }
1276
+ return refreshed;
1277
+ }
1278
+
1076
1279
  // ---------------------------------------------------------------------------
1077
1280
  // Multi-dep orchestrator
1078
1281
  // ---------------------------------------------------------------------------
@@ -1086,6 +1289,7 @@ type ScaffoldAllOptions = {
1086
1289
  dryRun?: boolean,
1087
1290
  cacheSlotLabel?: ?string,
1088
1291
  autolinkingJsonPath?: string,
1292
+ iosDeploymentTarget?: ?string,
1089
1293
  // npm dep names to skip entirely — used when the user declined the
1090
1294
  // confirmation prompt for first-time scaffolds. Skipped deps still
1091
1295
  // appear in the returned results array with status='skipped-opt-out'.
@@ -1113,62 +1317,29 @@ function scaffoldAll(
1113
1317
  return [];
1114
1318
  }
1115
1319
 
1116
- /*:: type AutolinkingJson = {dependencies?: ?{[string]: {root?: string, platforms?: {ios?: ?{...}, ...}, ...}}, ...}; */
1117
- // $FlowFixMe[incompatible-type] JSON.parse returns any
1118
- const data /*: AutolinkingJson */ = JSON.parse(
1119
- fs.readFileSync(autolinkingJsonPath, 'utf8'),
1120
- );
1121
- const deps = data.dependencies;
1122
- if (deps == null) {
1123
- return [];
1124
- }
1125
-
1126
- // Narrow the direct autolinking.json entries with an iOS platform, then
1127
- // expand transitive `spm.dependencies` so the scaffolder covers EXACTLY the
1128
- // set the autolinker considers. Without this, a transitive native dep that
1320
+ // The scaffolder covers EXACTLY the set the autolinker considers, transitive
1321
+ // `spm.dependencies` included. Without them, a transitive native dep that
1129
1322
  // ships no Package.swift would be flagged by the autolinker but never
1130
1323
  // scaffolded here — leaving `react-native spm scaffold` unable to clear the
1131
1324
  // autolinker's missing-manifest error.
1132
1325
  const results /*: Array<ScaffoldResult> */ = [];
1133
- const directDeps /*: Array<AutolinkedDep> */ = [];
1134
- for (const name of Object.keys(deps)) {
1135
- const raw = deps[name];
1136
- if (raw == null) continue;
1137
- const root = raw.root;
1138
- const ios = raw.platforms?.ios;
1139
- if (typeof root !== 'string' || ios == null) {
1326
+ // Outside collectAutolinkedDeps' own try: a malformed remote config
1327
+ // (RemoteVersionError) is a misconfiguration to surface, not an expansion
1328
+ // failure to degrade past.
1329
+ const remote = remotePackageConfig(appRoot);
1330
+ const allDeps = collectAutolinkedDeps({
1331
+ autolinkingJsonPath,
1332
+ remote,
1333
+ onSkipped: name => {
1140
1334
  results.push({
1141
1335
  depName: name,
1142
1336
  status: 'skipped-no-ios',
1143
1337
  reason: 'no iOS platform in autolinking.json',
1144
1338
  });
1145
- continue;
1146
- }
1147
- // $FlowFixMe[incompatible-type] `ios` shape is runtime-validated above
1148
- const iosPlatform /*: AutolinkingIosPlatform */ = ios;
1149
- directDeps.push({name, root, platforms: {ios: iosPlatform}});
1150
- }
1151
-
1152
- // Outside the try: a malformed remote config (RemoteVersionError) is a
1153
- // misconfiguration to surface, not an expansion failure to degrade past.
1154
- const remote = remotePackageConfig(appRoot);
1155
-
1156
- let allDeps /*: Array<AutolinkedDep> */ = [];
1157
- try {
1158
- allDeps = expandSpmDependencies(directDeps, {
1159
- readConfig: defaultReadConfig,
1160
- resolveDep: defaultResolveDep,
1161
- extraReservedNames: remote != null ? [remote.identity] : undefined,
1162
- log,
1163
- });
1164
- } catch (e) {
1165
- if (e instanceof SpmNameCollisionError) {
1166
- throw e;
1167
- }
1168
- // A transitive-resolution failure shouldn't abort the whole scaffold pass;
1169
- // fall back to the direct deps so at least those get manifests.
1170
- log(`Transitive spm.dependencies expansion failed: ${e.message}`);
1171
- allDeps = directDeps;
1339
+ },
1340
+ });
1341
+ if (allDeps == null) {
1342
+ return results;
1172
1343
  }
1173
1344
 
1174
1345
  // Index every autolinked dep's podspec name → its npm name, so a dep that
@@ -1187,14 +1358,8 @@ function scaffoldAll(
1187
1358
  // discover the podspec at the dep root so podspec-name sibling deps
1188
1359
  // (e.g. `s.dependency "TestLibraryCommon"`) still wire to their npm
1189
1360
  // package.
1190
- try {
1191
- for (const f of fs.readdirSync(dep.root)) {
1192
- if (f.endsWith('.podspec') && !f.startsWith('.spm-scaffold-')) {
1193
- podToNpm.set(path.basename(f, '.podspec'), dep.name);
1194
- }
1195
- }
1196
- } catch {
1197
- // unreadable dep root — sibling wiring falls back to the warning path
1361
+ for (const found of findPodspecs(dep.root)) {
1362
+ podToNpm.set(path.basename(found, '.podspec'), dep.name);
1198
1363
  }
1199
1364
  }
1200
1365
  }
@@ -1213,6 +1378,7 @@ function scaffoldAll(
1213
1378
  force: opts.force === true,
1214
1379
  dryRun: opts.dryRun === true,
1215
1380
  cacheSlotLabel: opts.cacheSlotLabel ?? null,
1381
+ iosDeploymentTarget: opts.iosDeploymentTarget ?? null,
1216
1382
  podToNpm,
1217
1383
  swiftNameByNpm,
1218
1384
  remote,
@@ -1238,6 +1404,7 @@ function scaffoldAll(
1238
1404
  }
1239
1405
 
1240
1406
  module.exports = {
1407
+ refreshScaffoldedPlatformFloors,
1241
1408
  scaffoldAll,
1242
1409
  scaffoldPackageSwiftForDep,
1243
1410
  translatePodspecToSpmTarget,
@@ -267,19 +267,16 @@ function findProjectObject(text /*: string */) /*: ObjectRange | null */ {
267
267
  return findObjectByUuid(text, m[1]);
268
268
  }
269
269
 
270
- /**
271
- * Every PBXNativeTarget whose productType is an application. Returns uuid +
272
- * name + body range for each. Used to pick the app target to inject into
273
- * (and to refuse on ambiguity).
274
- */
275
- function findApplicationTargets(
270
+ /** Visit every top-level object of a section, in file order. */
271
+ function forEachObjectInSection(
276
272
  text /*: string */,
277
- ) /*: Array<{uuid: string, name: string, bodyOpen: number, bodyClose: number}> */ {
278
- const section = findSection(text, 'PBXNativeTarget');
273
+ name /*: string */,
274
+ fn /*: (obj: {uuid: string, comment: ?string, bodyOpen: number, bodyClose: number}) => void */,
275
+ ) /*: void */ {
276
+ const section = findSection(text, name);
279
277
  if (section == null) {
280
- return [];
278
+ return;
281
279
  }
282
- const out = [];
283
280
  const re = /\n\t\t([0-9A-Fa-f]{24})(?: \/\* (.*?) \*\/)? = \{/g;
284
281
  re.lastIndex = section.contentStart;
285
282
  for (;;) {
@@ -287,25 +284,42 @@ function findApplicationTargets(
287
284
  if (m == null || m.index >= section.end) {
288
285
  break;
289
286
  }
290
- const uuid = m[1];
291
- const comment = m[2];
292
287
  const bodyOpen = text.indexOf('{', m.index);
293
288
  const bodyClose = scanToClose(text, bodyOpen);
294
- const obj = {uuid, bodyOpen, bodyClose};
295
- const productType = findField(text, obj, 'productType');
296
- if (
297
- productType != null &&
298
- /com\.apple\.product-type\.application/.test(productType.value)
299
- ) {
289
+ fn({uuid: m[1], comment: m[2], bodyOpen, bodyClose});
290
+ re.lastIndex = bodyClose;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Every PBXNativeTarget whose productType is an application. Returns uuid +
296
+ * name + body range for each. Used to pick the app target to inject into
297
+ * (and to refuse on ambiguity).
298
+ */
299
+ function findApplicationTargets(
300
+ text /*: string */,
301
+ ) /*: Array<{uuid: string, name: string, bodyOpen: number, bodyClose: number}> */ {
302
+ const out = [];
303
+ forEachObjectInSection(
304
+ text,
305
+ 'PBXNativeTarget',
306
+ ({uuid, comment, bodyOpen, bodyClose}) => {
307
+ const obj = {uuid, bodyOpen, bodyClose};
308
+ const productType = findField(text, obj, 'productType');
309
+ if (
310
+ productType == null ||
311
+ !/com\.apple\.product-type\.application/.test(productType.value)
312
+ ) {
313
+ return;
314
+ }
300
315
  const nameField = findField(text, obj, 'name');
301
316
  const name =
302
317
  nameField != null
303
318
  ? nameField.value.replace(/^"|"$/g, '')
304
319
  : (comment ?? uuid);
305
320
  out.push({uuid, name, bodyOpen, bodyClose});
306
- }
307
- re.lastIndex = bodyClose;
308
- }
321
+ },
322
+ );
309
323
  return out;
310
324
  }
311
325
 
@@ -766,6 +780,7 @@ module.exports = {
766
780
  findSection,
767
781
  findProjectObject,
768
782
  findApplicationTargets,
783
+ forEachObjectInSection,
769
784
  uuidsInArray,
770
785
  detectFieldIndent,
771
786
  insertObjectsIntoSection,