mcpField
C
capacitor-plugin-spm-support

capacitor-plugin-spm-support

github.com/cap-goDevelopment75728 installsUpdated 2w ago
Repository

About capacitor-plugin-spm-support

capacitor-plugin-spm-support is a skill for adding Swift Package Manager support to a Capacitor plugin while retaining CocoaPods support. It guides maintainers through source conversion, package configuration and validation.

Key features

  1. 01SPM manifest and target setup
  2. 02Swift plugin registration conversion
  3. 03CocoaPods podspec retention
  4. 04Dependency and resource configuration
  5. 05Package and app validation

Use cases

  1. 01Making a plugin installable in SPM-based apps
  2. 02Resolving missing Package.swift warnings
  3. 03Preparing plugin distribution for Capacitor 9
  4. 04Checking SPM and CocoaPods builds

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-plugin-spm-support

After installing, type/capacitor-plugin-spm-supportin Claude Code to confirm it loaded.

Bundled filesmetadata.jsonreferences/package-swift-templates.md

Details

From the project README

Add Swift Package Manager Support to a Capacitor Plugin

Make a plugin installable in SPM-based Capacitor apps while keeping its podspec for CocoaPods apps.

When to Use

TRIGGER when:

  • A plugin repo has ios/Plugin/*.swift + *.m/*.h with CAP_PLUGIN(...) and no root Package.swift.
  • An app's npx cap sync ios prints <plugin id> does not have a Package.swift for a plugin the user maintains.
  • A plugin works with CocoaPods but JS gets "X" plugin is not implemented on ios in SPM apps.
  • Preparing a plugin for Capacitor 9 / CocoaPods Trunk read-only.

Do not use:

  • App-side CocoaPods removal -> cocoapods-to-spm.
  • Capacitor major bumps of a plugin -> capacitor-plugin-upgrades, capacitor-plugin-upgrade-v8-to-v9.
  • Android-only plugin work, or non-Capacitor (pure Cordova, React Native) plugins.

Rules the CLI Enforces (non-obvious)

  • Package.swift must be at the npm package root (next to package.json), not under ios/. The app CLI only checks <plugin root>/Package.swift.
  • The CLI references the plugin in the app's CapApp-SPM/Package.swift as .package(name: "<Name>", path: ...) and .product(name: "<Name>", package: "<Name>"), where <Name> is derived from the npm name: drop @, turn / and - into word breaks, PascalCase. @capgo/capacitor-updater -> CapgoCapacitorUpdater, @acme/capacitor-foo-bar -> AcmeCapacitorFooBar. The Package(name:) and the .library(name:) product must both equal <Name>, or app builds fail to resolve the product.
  • Runtime registration uses ios/App/App/capacitor.config.json -> packageClassList, filled by scanning the plugin's iOS sources (capacitor.ios.src in package.json, default ios) for @objc(ClassName). Keep @objc(<Class>Plugin) on the class; without it the plugin is never registered.
  • The capacitor-swift-pm dependency must use from: "<major>.0.0". If the major differs from the app's Capacitor, app sync rewrites it in node_modules and warns <id> is built for Capacitor <N>, it might cause issues.
  • Capacitor 9: depend only on .product(name: "Capacitor", package: "capacitor-swift-pm"). Remove the unconditional Cordova product; it is only present in apps that install a Cordova plugin. Keep it on Capacitor 8 only if your code imports Cordova.

Procedure

1. Inspect

Read package.json (name, files, capacitor.ios.src, scripts), the .podspec (s.name, s.ios.deployment_target, s.dependency, s.resources, s.vendored_frameworks), every file in ios/, and the CAP_PLUGIN(...) macro in the .m file.

Record: SPM <Name> (rule above), class name, jsName, every CAP_PLUGIN_METHOD(name, returnType), iOS deployment target, third-party pods, resources, vendored frameworks, ObjC/C sources other than the bridge.

Check each third-party pod has an official SPM package. Report any that do not before editing; ask whether to drop, vendor as .xcframework, or keep the plugin CocoaPods-only.

2. Fast path: converter

If the iOS code is Swift-only apart from the [Name]Plugin.m / .h bridge, the official converter does most of the work (adds CAPBridgedPlugin, creates Package.swift, moves code to ios/Sources and ios/Tests, removes Plugin.xcodeproj/xcworkspace/Podfile, updates podspec source_files, files, and verify:ios): https://github.com/ionic-team/capacitor-plugin-converter. Review its diff against the rules above, especially the package/product name.

3. Manual conversion
  1. Move sources: ios/Plugin/ -> ios/Sources/<ClassName>/, tests -> ios/Tests/<ClassName>Tests/.
  2. Convert the class (keep method names and return types exactly as in the .m macro):
    import Capacitor
    
    @objc(FooPlugin)
    public class FooPlugin: CAPPlugin, CAPBridgedPlugin {
        public let identifier = "FooPlugin"     // first CAP_PLUGIN argument
        public let jsName = "Foo"               // second CAP_PLUGIN argument (registerPlugin name)
        public let pluginMethods: [CAPPluginMethod] = [
            CAPPluginMethod(name: "doThing", returnType: CAPPluginReturnPromise),
            CAPPluginMethod(name: "watch", returnType: CAPPluginReturnCallback),
            CAPPluginMethod(name: "fireAndForget", returnType: CAPPluginReturnNone)
        ]
        @objc func doThing(_ call: CAPPluginCall) { call.resolve() }
    }
    
  3. Delete FooPlugin.m and FooPlugin.h (the CAP_PLUGIN bridge) and stale Plugin.xcodeproj, Plugin.xcworkspace, Podfile, Info.plist files under ios/.
  4. Add root Package.swift. Template and variants (resources, binary targets, third-party packages, mixed ObjC) in references/package-swift-templates.md.
  5. Update the podspec: s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}', keep s.dependency 'Capacitor', and mirror deployment target and resources.
  6. Update package.json:
    • files: add ios/Sources/, Package.swift; keep the .podspec; remove old ios/Plugin/.
    • verify:ios: xcodebuild -scheme <Name> -destination generic/platform=iOS.

Verification

# Package resolves and builds standalone (from plugin root)
swift package describe >/dev/null && echo "manifest ok"
xcodebuild -scheme <Name> -destination generic/platform=iOS build
# Published tarball contains the manifest and sources
npm pack --dry-run 2>&1 | grep -E 'Package.swift|ios/Sources'
# No leftover ObjC bridge
grep -rn "CAP_PLUGIN(" ios/ && echo "LEFTOVER BRIDGE"

Then in an SPM example/test app (directory with capacitor.config.*): install the plugin (npm install ../path or a packed tarball), run npx cap sync ios, confirm no does not have a Package.swift warning, confirm <Name> appears in ios/App/CapApp-SPM/Package.swift and the class appears in ios/App/App/capacitor.config.json packageClassList, build, and call one method from JS. Also run pod lib lint (or a CocoaPods example app) if the podspec is still published.

Error Handling

ErrorFix
product '<Name>' required by package 'capapp-spm' target 'CapApp-SPM' not found in package '<Name>'Package(name:) / .library(name:) do not match the CLI-derived <Name>.
"Foo" plugin is not implemented on iosMissing @objc(FooPlugin), wrong jsName, or class not in packageClassList; re-sync after fixing.
"Foo.bar()" is not implemented on iosMethod missing from pluginMethods or not marked @objc.
No such module 'Capacitor' building the packageMissing capacitor-swift-pm dependency or wrong product name.
public headers ("include") directory path for 'X' is invalid or not contained in the targetMixed ObjC target without publicHeadersPath; split ObjC into its own target (see reference).
<id> is built for Capacitor <N>, it might cause issues (in app)Bump capacitor-swift-pm from: to the app's major.
Cordova product missing (Capacitor 9 app)Remove the Cordova product dependency.
Resource not found at runtimeDeclare resources: and load via Bundle.module, not Bundle.main.

References

Only load when the topic is in play:

  • references/package-swift-templates.md: base manifest, resources and privacy manifest, third-party SPM dependencies, binary .xcframework, mixed Swift/ObjC targets, Capacitor 8 vs 9 dependency blocks.

Comments

Markdown supported