Skip to content

Commit dc5bce5

Browse files
committed
moved command to npx react-native update/init/sync
1 parent 141060a commit dc5bce5

7 files changed

Lines changed: 193 additions & 75 deletions

File tree

packages/react-native/react-native.config.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,96 @@ const codegenCommand /*: Command */ = {
112112

113113
commands.push(codegenCommand);
114114

115+
const spmCommand /*: Command */ = {
116+
name: 'spm [action]',
117+
description:
118+
'Set up or maintain Swift Package Manager support for the iOS/macOS app. ' +
119+
'Actions: init, update, sync, clean, codegen, download. ' +
120+
'With no action: runs init if Package.swift is missing, otherwise update.',
121+
options: [
122+
{
123+
name: '--version <string>',
124+
description:
125+
'React Native version (e.g. 0.80.0). Defaults to the version in node_modules/react-native/package.json.',
126+
},
127+
{
128+
name: '--localXcframework <path>',
129+
description: 'Use a local React.xcframework instead of downloading.',
130+
},
131+
{
132+
name: '--artifactsDir <path>',
133+
description: 'Override the artifact cache directory.',
134+
},
135+
{
136+
name: '--flavor <string>',
137+
description: 'Artifact flavor: debug or release.',
138+
},
139+
{
140+
name: '--skipCodegen',
141+
description: 'Skip react-native codegen step.',
142+
},
143+
{
144+
name: '--skipDownload',
145+
description: 'Skip automatic artifact download.',
146+
},
147+
{
148+
name: '--forceDownload',
149+
description: 'Clear cached artifacts and re-download from Maven.',
150+
},
151+
{
152+
name: '--skipXcodeproj',
153+
description: 'Skip .xcodeproj generation.',
154+
},
155+
{
156+
name: '--bundleIdentifier <string>',
157+
description: 'Override CFBundleIdentifier in the generated Info.plist.',
158+
},
159+
{
160+
name: '--productName <string>',
161+
description: 'Override PRODUCT_NAME in the generated Info.plist.',
162+
},
163+
{
164+
name: '--entryFile <path>',
165+
description:
166+
'JS entry file relative to app root (default: package.json "main" or index.js).',
167+
},
168+
],
169+
func: async (argv, _config, args) => {
170+
const passthrough /*: Array<string> */ = [];
171+
if (argv.length > 0) {
172+
passthrough.push(argv[0]);
173+
}
174+
const stringOpts /*: Array<[string, string]> */ = [
175+
['version', '--version'],
176+
['localXcframework', '--local-xcframework'],
177+
['artifactsDir', '--artifacts-dir'],
178+
['flavor', '--flavor'],
179+
['bundleIdentifier', '--bundle-identifier'],
180+
['productName', '--product-name'],
181+
['entryFile', '--entry-file'],
182+
];
183+
for (const [key, flag] of stringOpts) {
184+
if (args[key] != null) {
185+
passthrough.push(flag, String(args[key]));
186+
}
187+
}
188+
const boolOpts /*: Array<[string, string]> */ = [
189+
['skipCodegen', '--skip-codegen'],
190+
['skipDownload', '--skip-download'],
191+
['forceDownload', '--force-download'],
192+
['skipXcodeproj', '--skip-xcodeproj'],
193+
];
194+
for (const [key, flag] of boolOpts) {
195+
if (args[key]) {
196+
passthrough.push(flag);
197+
}
198+
}
199+
await require('./scripts/setup-apple-spm').main(passthrough);
200+
},
201+
};
202+
203+
commands.push(spmCommand);
204+
115205
const config = {
116206
commands,
117207
platforms: {} /*:: as {[string]: Readonly<{

packages/react-native/scripts/setup-apple-spm.js

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
* generated packages, artifacts, and .xcodeproj.
2525
* update Regenerate generated packages/artifacts/project
2626
* without overwriting root Package.swift.
27+
* sync Lightweight sync invoked by the Xcode auto-sync
28+
* build phase: regenerates autolinking and
29+
* xcframeworks sub-packages and writes the
30+
* .spm-sync-stamp file. Skips .xcodeproj regen.
2731
* clean Remove generated SPM state only.
2832
* codegen Run only codegen and install the SPM template.
2933
* download Download/check xcframework artifacts only.
@@ -83,6 +87,7 @@ const {log, warn: logError} = makeLogger('setup-apple-spm');
8387
const VALID_ACTIONS = new Set([
8488
'init',
8589
'update',
90+
'sync',
8691
'clean',
8792
'codegen',
8893
'download',
@@ -104,7 +109,7 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
104109
type: 'string',
105110
choices: Array.from(VALID_ACTIONS),
106111
describe:
107-
'Action to run: init, update, clean, codegen, or download. Defaults to init when Package.swift is missing, otherwise update.',
112+
'Action to run: init, update, sync, clean, codegen, or download. Defaults to init when Package.swift is missing, otherwise update.',
108113
})
109114
.option('version', {
110115
type: 'string',
@@ -255,7 +260,7 @@ function cleanGeneratedState(appRoot /*: string */) {
255260
function resolveAction(
256261
requestedAction /*: SetupArgs['action'] */,
257262
appRoot /*: string */,
258-
) /*: 'init' | 'update' | 'clean' | 'codegen' | 'download' */ {
263+
) /*: 'init' | 'update' | 'sync' | 'clean' | 'codegen' | 'download' */ {
259264
if (requestedAction != null) {
260265
return requestedAction;
261266
}
@@ -511,10 +516,7 @@ function generateXcframeworksPackage(
511516
generatePackage(packageArgs);
512517
}
513518

514-
function warnForMissingPackageSwift(
515-
appRoot /*: string */,
516-
scriptsDir /*: string */,
517-
) {
519+
function warnForMissingPackageSwift(appRoot /*: string */) {
518520
const mainPackageSwift = path.join(appRoot, 'Package.swift');
519521
if (fs.existsSync(mainPackageSwift)) {
520522
return;
@@ -524,9 +526,7 @@ function warnForMissingPackageSwift(
524526
log(
525527
'\x1b[33mWARNING: Package.swift not found.\x1b[0m Run init to generate an initial one:',
526528
);
527-
log(
528-
` node ${path.relative(appRoot, path.join(scriptsDir, 'setup-apple-spm.js'))} init`,
529-
);
529+
log(' react-native spm init');
530530
log('');
531531
}
532532

@@ -622,7 +622,8 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
622622
return;
623623
}
624624

625-
const needsCliConfig = action === 'init' || action === 'update';
625+
const needsCliConfig =
626+
action === 'init' || action === 'update' || action === 'sync';
626627
const autolinkingConfigResult = needsCliConfig
627628
? loadAutolinkingConfig(projectRoot, appRoot)
628629
: null;
@@ -644,6 +645,22 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
644645
return;
645646
}
646647

648+
if (action === 'sync') {
649+
const {main: runSync} = require('./spm/sync-spm-autolinking');
650+
try {
651+
await runSync([
652+
'--app-root',
653+
appRoot,
654+
'--react-native-root',
655+
reactNativeRoot,
656+
]);
657+
} catch (e) {
658+
logError(`SPM sync failed: ${e.message}`);
659+
process.exitCode = 1;
660+
}
661+
return;
662+
}
663+
647664
let resolvedArtifactsDir = null;
648665
if (action === 'download') {
649666
try {
@@ -702,7 +719,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
702719
if (action === 'init') {
703720
ensureGitignoreSpmEntries(appRoot);
704721
} else {
705-
warnForMissingPackageSwift(appRoot, scriptsDir);
722+
warnForMissingPackageSwift(appRoot);
706723
}
707724
warnForMissingVfsOverlayFlags(appRoot);
708725

packages/react-native/scripts/spm/__doc__/rfc-spm-xcframework.md

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ open MyApp-SPM.xcodeproj
2626
```
2727

2828
A future CLI integration (e.g., an `--ios-build-system spm` flag on
29-
`react-native init`) could run `setup-apple-spm.js --init` automatically as part
29+
`react-native init`) could run `react-native spm init` automatically as part
3030
of project creation.
3131

3232
### Existing project
3333

3434
```bash
3535
cd MyApp/ios
36-
node ../node_modules/react-native/scripts/setup-apple-spm.js --init
36+
react-native spm init
3737
# CocoaPods setup continues to work — both can coexist
3838
open MyApp-SPM.xcodeproj
3939
```
@@ -118,7 +118,8 @@ goes read-only in December 2026.
118118

119119
### Pipeline
120120

121-
`setup-apple-spm.js` orchestrates five steps:
121+
`react-native spm` orchestrates five steps (the underlying script is
122+
`scripts/setup-apple-spm.js`):
122123

123124
| # | Step | Script | Output |
124125
|---|------|--------|--------|
@@ -129,15 +130,15 @@ goes read-only in December 2026.
129130
| 5 | Xcodeproj | `spm/generate-spm-xcodeproj.js` | `AppName-SPM.xcodeproj` |
130131
|| Sync (build-time) | `spm/sync-spm-autolinking.js` | Re-runs steps 1–4 when inputs change (downloads artifacts if missing) |
131132

132-
When run with `--init`, the script also appends SPM-specific entries to the
133+
When run with the `init` action, the script also appends SPM-specific entries to the
133134
project's `.gitignore` (e.g., `autolinked/`, `build/generated/ios/`,
134135
`build/xcframeworks/`, `.build/`, `Package.resolved`). Entries that already
135136
exist are not duplicated. This ensures generated artifacts are not accidentally
136137
committed.
137138

138139
### Auto-sync build phase
139140

140-
After initial setup, developers shouldn't need to re-run `setup-apple-spm.js`
141+
After initial setup, developers shouldn't need to re-run `react-native spm`
141142
manually when dependencies change. The generated `.xcodeproj` includes a
142143
**Sync SPM Autolinking** pre-build phase (ordered first, before VFS overlay)
143144
that:
@@ -165,8 +166,8 @@ new version's cache directory.
165166
The sync step is **self-healing**: if xcframework artifacts are missing (e.g.,
166167
the local cache at `~/Library/Caches/com.facebook.ReactNative/` was deleted,
167168
or the project was freshly cloned), it automatically downloads them before
168-
proceeding with autolinking and package generation. This means `setup-apple-spm.js`
169-
is only strictly required for initial project scaffolding (`--init`); subsequent
169+
proceeding with autolinking and package generation. This means `react-native spm`
170+
is only strictly required for initial project scaffolding (`init`); subsequent
170171
builds recover automatically.
171172

172173
### Cleaning generated SPM state
@@ -178,17 +179,15 @@ Xcode provides no hook to run custom scripts during GUI clean actions.
178179
To fully reset SPM state, run:
179180

180181
```bash
181-
node setup-apple-spm.js --clean
182+
react-native spm clean
182183
```
183184

184185
This removes `build/xcframeworks/`, `build/generated/ios/`, `autolinked/`, and
185-
`.build/`, then re-runs the full setup (codegen, download, autolinking, package
186-
generation). After it completes, open the `.xcodeproj` in Xcode and build.
187-
188-
The `--clean` flag performs a full re-setup rather than just deleting files
189-
because SPM package resolution is locked for the duration of a build — if only
190-
stubs were left in place, Xcode would resolve stubs and never pick up the real
191-
packages generated by the sync build phase.
186+
`.build/`. Then run `react-native spm update` (or open the checked-in
187+
`.xcodeproj` and build) to regenerate state. SPM package resolution is locked
188+
for the duration of a build — if only stubs were left in place, Xcode would
189+
resolve stubs and never pick up the real packages generated by the sync build
190+
phase.
192191

193192
### Stub packages for fresh clones
194193

@@ -241,16 +240,16 @@ AppName-SPM.xcodeproj
241240
└── build/generated/ios/ (codegen source path)
242241
```
243242

244-
The root `Package.swift` is generated once (`--init`) and committed. Developers
243+
The root `Package.swift` is generated once (`react-native spm init`) and committed. Developers
245244
can customize it — add dependencies, adjust settings, or add new targets.
246245
Subsequent runs only regenerate the sub-packages (`autolinked/` and
247246
`build/xcframeworks/`).
248247

249248
Both `Package.swift` and `AppName-SPM.xcodeproj` are generated once during
250-
`--init` and committed. This is typically done by project scaffolding tools
251-
(`react-native init`, `expo init`) rather than by developers manually. After
252-
initial generation, these files are user-owned — subsequent `setup-apple-spm.js`
253-
runs (without `--init`) only regenerate the sub-packages, not the root
249+
`react-native spm init` and committed. This is typically done by project
250+
scaffolding tools (`react-native init`, `expo init`) rather than by developers
251+
manually. After initial generation, these files are user-owned — subsequent
252+
`react-native spm update` runs only regenerate the sub-packages, not the root
254253
`Package.swift` or `.xcodeproj`. Teammates can clone the repo and open Xcode
255254
immediately — stub packages allow package resolution to succeed, and the
256255
auto-sync build phase downloads artifacts and handles autolinking on the first
@@ -349,7 +348,7 @@ module.exports = {
349348
};
350349
```
351350

352-
**Planned (Phase 2):** When `setup-apple-spm.js` gains third-party library
351+
**Planned (Phase 2):** When `react-native spm` gains third-party library
353352
support, it will:
354353
1. If `spm.xcframework` is declared, download the prebuilt binary (fast path).
355354
2. If the download fails or the `--source` flag is passed, fall back to
@@ -486,7 +485,7 @@ compilation support is a goal for specific use cases:
486485

487486
The source compilation path would reuse the same SPM package structure but
488487
replace binary xcframework targets with source targets. This is planned as a
489-
`--source` flag to `setup-apple-spm.js`.
488+
`--source` flag to `react-native spm`.
490489

491490
### SPM build tool plugins
492491

@@ -542,7 +541,7 @@ required; libraries that don't provide them fall back to source compilation.
542541

543542
### Migration path for existing apps
544543

545-
1. Run `setup-apple-spm.js --init` in the `ios/` directory.
544+
1. Run `react-native spm init` in the `ios/` directory.
546545
2. Commit the generated `Package.swift` and `AppName-SPM.xcodeproj`.
547546
3. Open the new `.xcodeproj` and build.
548547
4. Once validated, optionally remove CocoaPods files (`Podfile`, `Pods/`,
@@ -559,7 +558,7 @@ Xcode build and re-runs the sync step automatically. This downloads the new
559558
version's xcframeworks, regenerates the sub-packages, and updates autolinking.
560559
No manual edits to `Package.swift` are needed — the root `Package.swift` only
561560
references sub-packages by relative path, and those sub-packages are fully
562-
regenerated each run. Developers can also run `setup-apple-spm.js` manually to
561+
regenerated each run. Developers can also run `react-native spm` manually to
563562
trigger the update before building.
564563

565564
## How we teach this
@@ -576,10 +575,10 @@ trigger the update before building.
576575

577576
### CLI discoverability
578577

579-
- `setup-apple-spm.js --help` should provide clear usage instructions and
578+
- `react-native spm --help` should provide clear usage instructions and
580579
explain each step.
581-
- Error messages should include actionable suggestions (e.g., "Run with --init
582-
for first-time setup").
580+
- Error messages should include actionable suggestions (e.g., "Run
581+
`react-native spm init` for first-time setup").
583582
- The auto-sync build phase should surface warnings in Xcode's issue navigator
584583
when autolinking state is stale.
585584

@@ -623,7 +622,7 @@ trigger the update before building.
623622
sources compile as an SPM target without producing a full release artifact.
624623
This would be useful for CI checks on pull requests.
625624

626-
5. **Hardening `--init` for existing projects.** Running `--init` on a project
625+
5. **Hardening `init` for existing projects.** Running `init` on a project
627626
that already has `Package.swift` or `.xcodeproj` should detect existing
628627
files and avoid overwriting user modifications. The current `.xcodeproj`
629628
generation uses a simple template-based approach; adopting a proper Xcode

0 commit comments

Comments
 (0)