Capacitor Version
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 8.3.1
@capacitor/core: 8.3.1
@capacitor/android: 8.3.1
@capacitor/ios: 8.3.1
Installed Dependencies:
@capacitor/cli: 8.1.0
@capacitor/core: 8.1.0
@capacitor/ios: 8.1.0
@capacitor/android: 8.1.0
[success] iOS looking great! 👌
[success] Android looking great! 👌
Other API Details
npm --version output: 10.9.4
node --version output: v22.22.0
Platforms Affected
Current Behavior
When two Capacitor plugins are installed and the last path components of their npm package directories collide, the CLI-generated CapApp-SPM/Package.swift declares two local-path packages whose SwiftPM identity (the lowercased path basename) is the same. SwiftPM rejects this.
The collision can take two forms with different severities:
Example A — hard error (two local plugins). @capacitor/app + @capacitor-firebase/app. Both resolve to npm directories ending in app, so both get SwiftPM identity app:
Conflicting identity for app: dependency '.../node_modules/@capacitor/app'
and dependency '.../node_modules/@capacitor-firebase/app' both point to
the same package identity 'app'.
The build fails. This is the long-standing local-vs-local hard-error path in SwiftPM and is unrelated to any specific Xcode version.
Example B — warning, future hard error (local plugin vs. transitive remote dependency). @capacitor-firebase/app-check + @capacitor-firebase/authentication. The Authentication plugin pulls in firebase-ios-sdk, which transitively depends on github.com/google/app-check. Both that remote package and the local App Check plugin directory get identity app-check:
Conflicting identity for app-check: dependency 'github.com/google/app-check'
and dependency '.../node_modules/@capacitor-firebase/app-check' both point
to the same package identity 'app-check'. This will be escalated to an
error in future versions of SwiftPM.
Currently a warning, but SwiftPM itself states it will become an error.
Root cause. The CLI emits .package(name: "<plugin.ios.name>", path: "<plugin.rootPath>") in cli/src/util/spm.ts. SwiftPM ignores the name: argument for local-path packages (post-SE-0292) and derives identity from the path's lowercased basename. The basename equals the npm package directory name and cannot be changed without renaming the npm package — which is a breaking change for plugin consumers.
Expected Behavior
Plugin authors should be able to point the CLI at a uniquely-named subdirectory containing Package.swift, so SwiftPM derives a unique identity from that subdirectory's basename instead of the npm package directory's basename.
Concretely: a plugin's package.json declares e.g.
{
"capacitor": {
"ios": {
"src": "ios",
"spm": { "path": "ios/spm/CapacitorFirebaseAppCheck" }
}
}
}
The CLI would then discover Package.swift at <rootPath>/ios/spm/CapacitorFirebaseAppCheck/Package.swift and emit:
.package(name: "CapacitorFirebaseAppCheck", path: "<rel>/.../node_modules/@capacitor-firebase/app-check/ios/spm/CapacitorFirebaseAppCheck")
SwiftPM identity becomes capacitorfirebaseappcheck → no collision in either Example A or Example B. Plugins that do not set capacitor.ios.spm.path keep their current behavior, so the change is fully backward compatible.
Project Reproduction
https://github.com/capawesome-team/capacitor-firebase-plugin-demo/tree/chore/ios-spm-migration
Additional Information
Capacitor Version
Other API Details
Platforms Affected
Current Behavior
When two Capacitor plugins are installed and the last path components of their npm package directories collide, the CLI-generated
CapApp-SPM/Package.swiftdeclares two local-path packages whose SwiftPM identity (the lowercased path basename) is the same. SwiftPM rejects this.The collision can take two forms with different severities:
Example A — hard error (two local plugins).
@capacitor/app+@capacitor-firebase/app. Both resolve to npm directories ending inapp, so both get SwiftPM identityapp:The build fails. This is the long-standing local-vs-local hard-error path in SwiftPM and is unrelated to any specific Xcode version.
Example B — warning, future hard error (local plugin vs. transitive remote dependency).
@capacitor-firebase/app-check+@capacitor-firebase/authentication. The Authentication plugin pulls infirebase-ios-sdk, which transitively depends ongithub.com/google/app-check. Both that remote package and the local App Check plugin directory get identityapp-check:Currently a warning, but SwiftPM itself states it will become an error.
Root cause. The CLI emits
.package(name: "<plugin.ios.name>", path: "<plugin.rootPath>")incli/src/util/spm.ts. SwiftPM ignores thename:argument for local-path packages (post-SE-0292) and derives identity from the path's lowercased basename. The basename equals the npm package directory name and cannot be changed without renaming the npm package — which is a breaking change for plugin consumers.Expected Behavior
Plugin authors should be able to point the CLI at a uniquely-named subdirectory containing
Package.swift, so SwiftPM derives a unique identity from that subdirectory's basename instead of the npm package directory's basename.Concretely: a plugin's
package.jsondeclares e.g.{ "capacitor": { "ios": { "src": "ios", "spm": { "path": "ios/spm/CapacitorFirebaseAppCheck" } } } }The CLI would then discover
Package.swiftat<rootPath>/ios/spm/CapacitorFirebaseAppCheck/Package.swiftand emit:SwiftPM identity becomes
capacitorfirebaseappcheck→ no collision in either Example A or Example B. Plugins that do not setcapacitor.ios.spm.pathkeep their current behavior, so the change is fully backward compatible.Project Reproduction
https://github.com/capawesome-team/capacitor-firebase-plugin-demo/tree/chore/ios-spm-migration
Additional Information