[Bug]: `cap sync ios` doesn't support Xcode 27.2's JSON project format (`project.xcproj`)
Capacitor Version
Capacitor Doctor
Latest Dependencies:
@capacitor/cli: 8.5.2
@capacitor/core: 8.5.2
@capacitor/android: 8.5.2
@capacitor/ios: 8.5.2
Installed Dependencies:
@capacitor/android: not installed
@capacitor/core: 8.5.2
@capacitor/cli: 8.5.2
@capacitor/ios: 8.5.2
[success] iOS looking great! Other API Details
Xcode 27.2 beta (27B5019j), also reproduced building with Xcode 27.0
macOS 27.0
Node 24.15.0
Swift Package Manager (the default for new Capacitor 8 apps)Platforms Affected
- iOS
- Android
- Web
Current Behavior
Xcode 27.2 beta adds a JSON-based project format. Its release notes say it's "more readable, merge-friendly, and easier for coding agents to edit", and projects in this format also open in Xcode 27.0. Converting a project replaces App.xcodeproj/project.pbxproj with App.xcodeproj/project.xcproj.
The CLI still reads project.pbxproj directly, so after converting:
npx cap sync ioslogsUnable to write to …/CapApp-SPM/Package.swiftwithENOENT: no such file or directory, open '…/App.xcodeproj/project.pbxproj', but exits with code 0.CapApp-SPM/Package.swiftis never rewritten, so plugins you add or remove never reach iOS.- After removing a plugin, Xcode then fails with
Could not resolve package dependencies, becausePackage.swiftstill points at the removed package. npx cap doctorstill reports "iOS looking great".
Xcode 27.0 and 27.2 both build the converted project fine, so only the CLI is affected.
Expected Behavior
The CLI works with both project.pbxproj and project.xcproj. At minimum, it should fail with a non-zero exit code and a clear message instead of reporting success.
Project Reproduction
With a blank app, no plugins of mine involved:
mkdir blank-cap && cd blank-cap && mkdir www && echo '<h1>hi</h1>' > www/index.html
npm init -y
npm install @capacitor/[email protected] @capacitor/[email protected] @capacitor/[email protected]
npm install -D @capacitor/[email protected]
echo '{ "appId": "com.example.blank", "appName": "blank", "webDir": "www" }' > capacitor.config.json
npx cap add ios
npx cap sync ios # works
# Convert to the new format (Xcode 27.2 beta); the same option is in Xcode's File inspector
xcodebuild -convert-project "Xcode Project" -project ios/App/App.xcodeproj
npm uninstall @capacitor/app
npx cap sync ios # logs the ENOENT error above, exits 0
grep CapacitorApp ios/App/CapApp-SPM/Package.swift # the removed plugin is still listed
xcodebuild -project ios/App/App.xcodeproj -scheme App -destination 'generic/platform=iOS Simulator' build
# error: Could not resolve package dependenciesAdditional Information
Places on main that read project.pbxproj:
| File | What it does | Effect with project.xcproj |
|---|---|---|
cli/src/ios/common.ts, getMajoriOSVersion() |
Reads IPHONEOS_DEPLOYMENT_TARGET |
Throws. Called from util/spm.ts (this bug) and ios/update.ts |
cli/src/ios/common.ts, editProjectSettingsIOS() |
Sets PRODUCT_BUNDLE_IDENTIFIER on cap add ios |
Would throw (new projects are still .pbxproj for now) |
cli/src/tasks/migrate.ts |
Bumps the deployment target | Throws |
cli/src/tasks/migrate-uiscene.ts + cli/src/util/xcode.ts |
Adds SceneDelegate.swift through the xcode package, which only parses .pbxproj |
Throws |
cli/src/config.ts, determineIOSWebDirAbs() |
Checks where public lives |
Error is caught, no effect |
The new file keeps the same build settings as JSON keys, for example:
"IPHONEOS_DEPLOYMENT_TARGET": "15.0",
"PRODUCT_BUNDLE_IDENTIFIER": "com.example.blank",It allows trailing commas, so JSON.parse can't read it as-is. For the deployment target and bundle ID, falling back to project.xcproj when project.pbxproj is missing, with regexes that accept both KEY = value; and "KEY": "value", would cover cap sync, cap add and cap migrate. The UIScene migration needs its own change, since the xcode package can't edit the new file.
Source: ionic-team/capacitor