2020 * node node_modules/react-native/scripts/setup-apple-spm.js [action] [options]
2121 *
2222 * Actions:
23- * init First-time setup: generate root Package.swift,
24- * generated packages, artifacts, and .xcodeproj.
25- * update Regenerate generated packages/artifacts/project
26- * without overwriting root Package.swift.
23+ * init First-time setup: generate sub-packages,
24+ * artifacts, and .xcodeproj. Adds SPM entries
25+ * to .gitignore.
26+ * update Regenerate sub-packages/artifacts. Does NOT
27+ * touch <App>-SPM.xcodeproj (it's committed —
28+ * see below). Pass --force-xcodeproj to opt in.
2729 * sync Lightweight sync invoked by the Xcode auto-sync
2830 * build phase: regenerates autolinking and
2931 * xcframeworks sub-packages and writes the
5153 * 2. generate-spm-autolinking-config.js → build/generated/autolinking/autolinking.json
5254 * 3. generate-spm-autolinking.js → build/generated/autolinking/Package.swift
5355 * 4. download-spm-artifacts.js → <artifacts-dir>/ (skipped if already present)
54- * 5. generate-spm-package.js → build/xcframeworks/Package.swift + symlinks (+ main Package.swift with init)
55- * 6. generate-spm-xcodeproj.js → <AppName>-SPM.xcodeproj (skipped with --skip-xcodeproj)
56+ * 5. generate-spm-package.js → build/xcframeworks/Package.swift + symlinks
57+ * 6. generate-spm-xcodeproj.js → <AppName>-SPM.xcodeproj (create-if-missing;
58+ * skipped with --skip-xcodeproj; opt back
59+ * into overwrite with --force-xcodeproj)
5660 *
57- * The main Package.swift is committed by the developer and NOT overwritten on
58- * subsequent runs. Use init for first-time setup to generate an initial one.
61+ * Commit policy: <AppName>-SPM.xcodeproj is COMMITTED to your repo, like
62+ * the legacy <AppName>.xcodeproj. It holds your signing, capabilities,
63+ * Build Phases, schemes — edit it in Xcode the normal way. Its
64+ * XCLocalSwiftPackageReference entries point at three stable sub-package
65+ * paths under build/ (xcframeworks, generated/autolinking, generated/ios);
66+ * adding/removing community deps changes the contents of those sub-packages
67+ * (gitignored) and never requires regenerating the xcodeproj. No app-level
68+ * Package.swift is generated or required.
5969 *
6070 * After running: open <AppName>-SPM.xcodeproj in Xcode.
6171 */
@@ -158,6 +168,12 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
158168 default : false ,
159169 describe : 'Skip .xcodeproj generation step' ,
160170 } )
171+ . option ( 'force-xcodeproj' , {
172+ type : 'boolean' ,
173+ default : false ,
174+ describe :
175+ 'Regenerate <App>-SPM.xcodeproj even if it already exists. WARNING: overwrites in-place Xcode edits (signing, capabilities, build phases). The xcodeproj is committed to your repo; SPM references stable sub-package paths under build/, so regeneration is not normally needed.' ,
176+ } )
161177 . option ( 'bundle-identifier' , {
162178 type : 'string' ,
163179 describe : 'Override CFBundleIdentifier in generated Info.plist' ,
@@ -175,7 +191,8 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
175191 . option ( 'project' , {
176192 type : 'boolean' ,
177193 default : false ,
178- describe : '[clean] Also remove Package.swift and <App>-SPM.xcodeproj/' ,
194+ describe :
195+ '[clean] Also remove the committed <App>-SPM.xcodeproj/ (will prompt for confirmation; bypass with --yes)' ,
179196 } )
180197 . option ( 'derived-data' , {
181198 type : 'boolean' ,
@@ -232,6 +249,7 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
232249 skipDownload : parsed [ 'skip-download' ] ,
233250 forceDownload : parsed [ 'force-download' ] ,
234251 skipXcodeproj : parsed [ 'skip-xcodeproj' ] ,
252+ forceXcodeproj : parsed [ 'force-xcodeproj' ] ,
235253 bundleIdentifier : parsed [ 'bundle-identifier' ] ?? null ,
236254 productName : parsed [ 'product-name' ] ?? null ,
237255 entryFile : parsed [ 'entry-file' ] ?? null ,
@@ -327,10 +345,6 @@ function gatherCleanTargets(
327345 }
328346
329347 if ( opts . project === true ) {
330- targets . push ( {
331- path : path . join ( appRoot , 'Package.swift' ) ,
332- label : 'Package.swift' ,
333- } ) ;
334348 for ( const name of xcodeprojNames ) {
335349 targets . push ( { path : path . join ( appRoot , name ) , label : `${ name } /` } ) ;
336350 }
@@ -1045,7 +1059,6 @@ function generateXcframeworksPackage(
10451059 reactNativeRoot /*: string */ ,
10461060 version /*: string */ ,
10471061 resolvedArtifactsDir /*: string | null */ ,
1048- shouldInit /*: boolean */ ,
10491062) {
10501063 log ( 'Generating xcframeworks sub-package...' ) ;
10511064 const packageArgs = [
@@ -1062,50 +1075,9 @@ function generateXcframeworksPackage(
10621075 if ( resolvedArtifactsDir != null ) {
10631076 packageArgs . push ( '--artifacts-dir' , resolvedArtifactsDir ) ;
10641077 }
1065- if ( shouldInit ) {
1066- packageArgs . push ( '--init' ) ;
1067- }
10681078 generatePackage ( packageArgs ) ;
10691079}
10701080
1071- function warnForMissingPackageSwift ( appRoot /*: string */ ) {
1072- const mainPackageSwift = path . join ( appRoot , 'Package.swift' ) ;
1073- if ( fs . existsSync ( mainPackageSwift ) ) {
1074- return ;
1075- }
1076-
1077- log ( '' ) ;
1078- log (
1079- '\x1b[33mWARNING: Package.swift not found.\x1b[0m Run init to generate an initial one:' ,
1080- ) ;
1081- log ( ' react-native spm init' ) ;
1082- log ( '' ) ;
1083- }
1084-
1085- function warnForMissingVfsOverlayFlags ( appRoot /*: string */ ) {
1086- const mainPackageSwift = path . join ( appRoot , 'Package.swift' ) ;
1087- if ( ! fs . existsSync ( mainPackageSwift ) ) {
1088- return ;
1089- }
1090-
1091- const pkgContent = fs . readFileSync ( mainPackageSwift , 'utf8' ) ;
1092- if ( ! pkgContent . includes ( 'ivfsoverlay' ) ) {
1093- log ( '' ) ;
1094- log (
1095- '\x1b[33mWARNING: Your Package.swift does not include -ivfsoverlay flags.\x1b[0m' ,
1096- ) ;
1097- log ( 'Add the following to your Package.swift for stable header identity:' ) ;
1098- log ( '' ) ;
1099- log ( ' let vfsOverlay = packageDir + "/build/xcframeworks/React-VFS.yaml"' ) ;
1100- log ( '' ) ;
1101- log ( ' // Add to cFlags:' ) ;
1102- log ( ' "-ivfsoverlay", vfsOverlay' ) ;
1103- log ( ' // Add to swiftFlags:' ) ;
1104- log ( ' "-Xcc", "-ivfsoverlay", "-Xcc", vfsOverlay' ) ;
1105- log ( '' ) ;
1106- }
1107- }
1108-
11091081function generateXcodeProject (
11101082 args /*: SetupArgs */ ,
11111083 appRoot /*: string */ ,
@@ -1116,7 +1088,25 @@ function generateXcodeProject(
11161088 return ;
11171089 }
11181090
1119- log ( 'Generating .xcodeproj...' ) ;
1091+ // The xcodeproj is committed; its XCLocalSwiftPackageReference entries
1092+ // point at three stable sub-package paths under build/, so adding/removing
1093+ // community deps never requires regenerating it. Regenerate only when
1094+ // missing (fresh clone / first init) or when the user explicitly opts in
1095+ // via --force-xcodeproj.
1096+ const existing = findExistingSpmXcodeproj ( appRoot ) ;
1097+ if ( existing != null && ! args . forceXcodeproj ) {
1098+ log (
1099+ `Found existing ${ path . basename ( existing ) } ; skipping regeneration ` +
1100+ `(pass --force-xcodeproj to overwrite, e.g. after deleting it).` ,
1101+ ) ;
1102+ return ;
1103+ }
1104+
1105+ log (
1106+ existing != null
1107+ ? `Regenerating .xcodeproj (--force-xcodeproj will overwrite Xcode-side edits)...`
1108+ : 'Generating .xcodeproj...' ,
1109+ ) ;
11201110 const xcodeprojArgs = [
11211111 '--app-root' ,
11221112 appRoot ,
@@ -1135,6 +1125,26 @@ function generateXcodeProject(
11351125 generateXcodeproj ( xcodeprojArgs ) ;
11361126}
11371127
1128+ /**
1129+ * Returns the absolute path to the first `*-SPM.xcodeproj/` directory found
1130+ * directly inside `appRoot`, or null if none exists.
1131+ */
1132+ function findExistingSpmXcodeproj ( appRoot /*: string */ ) /*: string | null */ {
1133+ try {
1134+ const entries /*: Array<{name: string, isDirectory(): boolean}> */ =
1135+ // $FlowFixMe[incompatible-type] Dirent typing
1136+ fs . readdirSync ( appRoot , { withFileTypes : true } ) ;
1137+ for ( const entry of entries ) {
1138+ if ( entry . isDirectory ( ) && entry . name . endsWith ( '-SPM.xcodeproj' ) ) {
1139+ return path . join ( appRoot , entry . name ) ;
1140+ }
1141+ }
1142+ } catch {
1143+ /* appRoot may not exist on init */
1144+ }
1145+ return null ;
1146+ }
1147+
11381148function logNextSteps (
11391149 projectRoot /*: string */ ,
11401150 appRoot /*: string */ ,
@@ -1202,9 +1212,12 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
12021212 }
12031213 }
12041214
1205- // Destructive scopes (DerivedData / cache) touch state outside appRoot.
1206- // Ask for confirmation unless --yes is passed or stdin isn't a TTY.
1207- const isDestructive = wantDerivedData || wantCache ;
1215+ // Destructive scopes ask for confirmation unless --yes is passed:
1216+ // --derived-data / --cache touch state outside appRoot
1217+ // --project removes the committed <App>-SPM.xcodeproj/
1218+ // The xcodeproj carries the user's signing, capabilities, build phases
1219+ // and is committed to the repo — deleting it loses Xcode-side edits.
1220+ const isDestructive = wantDerivedData || wantCache || wantProject ;
12081221 if ( isDestructive && ! args . cleanYes ) {
12091222 const targets = gatherCleanTargets ( appRoot , cleanOpts ) . filter ( t =>
12101223 fs . existsSync ( t . path ) ,
@@ -1317,7 +1330,6 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
13171330 reactNativeRoot ,
13181331 version ,
13191332 resolvedArtifactsDir ,
1320- action === 'init' ,
13211333 ) ;
13221334 } catch ( e ) {
13231335 logError ( `generate-spm-package.js failed: ${ e . message } ` ) ;
@@ -1329,10 +1341,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
13291341
13301342 if ( action === 'init' ) {
13311343 ensureGitignoreSpmEntries ( appRoot ) ;
1332- } else {
1333- warnForMissingPackageSwift ( appRoot ) ;
13341344 }
1335- warnForMissingVfsOverlayFlags ( appRoot ) ;
13361345
13371346 try {
13381347 generateXcodeProject ( args , appRoot , reactNativeRoot ) ;
@@ -1367,6 +1376,7 @@ module.exports = {
13671376 cleanGeneratedState,
13681377 gatherCleanTargets,
13691378 describeRnRootMismatch,
1379+ findExistingSpmXcodeproj,
13701380 findLegacyXcodeproj,
13711381 podfileNeedsPatch,
13721382} ;
0 commit comments