@@ -1131,6 +1131,77 @@ function scaffoldPackageSwiftForDep(
11311131 } ;
11321132}
11331133
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+
11341205// The `.iOS(...)` element of the emitted platforms array — the `.v15` enum form
11351206// pre-v20 scaffolds carry included, and without the array's closing bracket so
11361207// a user-extended array (`[.iOS(…), .macOS(…)]`) is still matched.
@@ -1152,24 +1223,23 @@ function refreshScaffoldedPlatformFloors(
11521223 path . join ( appRoot , 'build' , 'generated' , 'autolinking' , 'autolinking.json' ) ;
11531224 const refreshed = [ ] ;
11541225
1155- let deps ;
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 ;
11561230 try {
1157- // $FlowFixMe[incompatible-type] JSON.parse returns any
1158- deps = JSON . parse (
1159- fs . readFileSync ( autolinkingJsonPath , 'utf8' ) ,
1160- ) . dependencies ;
1231+ deps = collectAutolinkedDeps ( {
1232+ autolinkingJsonPath ,
1233+ remote : remotePackageConfig ( appRoot ) ,
1234+ } ) ;
11611235 } catch {
11621236 return refreshed ;
11631237 }
11641238 if ( deps == null ) {
11651239 return refreshed ;
11661240 }
11671241
1168- for ( const depName of Object . keys ( deps ) ) {
1169- const root = deps [ depName ] ?. root ;
1170- if ( typeof root !== 'string' ) {
1171- continue ;
1172- }
1242+ for ( const { name : depName , root} of deps ) {
11731243 const manifestPath = path . join ( root , 'Package.swift' ) ;
11741244 let content ;
11751245 try {
@@ -1247,76 +1317,29 @@ function scaffoldAll(
12471317 return [ ] ;
12481318 }
12491319
1250- /*:: type AutolinkingJson = {dependencies?: ?{[string]: {root?: string, platforms?: {ios?: ?{...}, ...}, ... }}, ... }; */
1251- // $FlowFixMe[incompatible-type] JSON.parse returns any
1252- const data /*: AutolinkingJson */ = JSON . parse (
1253- fs . readFileSync ( autolinkingJsonPath , 'utf8' ) ,
1254- ) ;
1255- const deps = data . dependencies ;
1256- if ( deps == null ) {
1257- return [ ] ;
1258- }
1259-
1260- // Narrow the direct autolinking.json entries with an iOS platform, then
1261- // expand transitive `spm.dependencies` so the scaffolder covers EXACTLY the
1262- // 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
12631322 // ships no Package.swift would be flagged by the autolinker but never
12641323 // scaffolded here — leaving `react-native spm scaffold` unable to clear the
12651324 // autolinker's missing-manifest error.
12661325 const results /*: Array<ScaffoldResult> */ = [ ] ;
1267- const directDeps /*: Array<AutolinkedDep> */ = [ ] ;
1268- for ( const name of Object . keys ( deps ) ) {
1269- const raw = deps [ name ] ;
1270- if ( raw == null ) continue ;
1271- const root = raw . root ;
1272- const ios = raw . platforms ?. ios ;
1273- 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 => {
12741334 results . push ( {
12751335 depName : name ,
12761336 status : 'skipped-no-ios' ,
12771337 reason : 'no iOS platform in autolinking.json' ,
12781338 } ) ;
1279- continue ;
1280- }
1281- // $FlowFixMe[incompatible-type] `ios` shape is runtime-validated above
1282- const iosPlatform /*: AutolinkingIosPlatform */ = ios ;
1283- directDeps . push ( { name, root, platforms : { ios : iosPlatform } } ) ;
1284- }
1285-
1286- // Outside the try: a malformed remote config (RemoteVersionError) is a
1287- // misconfiguration to surface, not an expansion failure to degrade past.
1288- const remote = remotePackageConfig ( appRoot ) ;
1289-
1290- let allDeps /*: Array<AutolinkedDep> */ = [ ] ;
1291- try {
1292- allDeps = expandSpmDependencies ( directDeps , {
1293- readConfig : defaultReadConfig ,
1294- resolveDep : defaultResolveDep ,
1295- readPodspec : defaultReadPodspec ,
1296- extraReservedNames : remote != null ? [ remote . identity ] : undefined ,
1297- } ) ;
1298- } catch ( e ) {
1299- if ( e instanceof SpmNameCollisionError ) {
1300- throw e ;
1301- }
1302- // A transitive-resolution failure shouldn't abort the whole scaffold pass;
1303- // fall back to the direct deps so at least those get manifests. They are
1304- // named the same way the autolinker names them — a manifest written under
1305- // any other name outlives this error on disk and then fails to resolve.
1306- log ( `Transitive dependency expansion failed: ${ e . message } ` ) ;
1307- allDeps = directDeps . map ( dep => {
1308- const resolved = resolveSwiftName (
1309- dep . name ,
1310- readSwiftpmConfig ( dep . root , defaultReadConfig ( dep . root ) ) ,
1311- defaultReadPodspec ( dep . root , dep . platforms . ios . podspecPath ) ,
1312- ) ;
1313- return {
1314- ...dep ,
1315- swiftName : resolved . name ,
1316- swiftNameSource : resolved . source ,
1317- swiftNamePodspecKey : resolved . podspecKey ,
1318- } ;
1319- } ) ;
1339+ } ,
1340+ } ) ;
1341+ if ( allDeps == null ) {
1342+ return results ;
13201343 }
13211344
13221345 // Index every autolinked dep's podspec name → its npm name, so a dep that
0 commit comments