Skip to content

chore: sync upstream PR #8524 - fix(ios): remove Cordova.framework from Capacitor project - #82

Open
riderx wants to merge 84 commits into
plusfrom
sync/upstream-pr-8524
Open

chore: sync upstream PR #8524 - fix(ios): remove Cordova.framework from Capacitor project#82
riderx wants to merge 84 commits into
plusfrom
sync/upstream-pr-8524

Conversation

@riderx

@riderx riderx commented Jul 7, 2026

Copy link
Copy Markdown
Member

Merge Conflict Resolution Required

The sync of upstream PR ionic-team#8524 from @jcesarmobile encountered merge conflicts.

Original PR: ionic-team#8524

What happened

  • Claude Code attempted to resolve the merge conflicts
  • This PR was created so CI and manual review can finish the sync safely

Synced from upstream by Capacitor+ Bot

Summary by CodeRabbit

  • New Features

    • Added support for loading a custom URL in the app during run.
    • Added broader Cordova plugin support on Android and iOS, including new bridge handling and package generation improvements.
    • Added a new API for reading double values from plugin configuration.
  • Bug Fixes

    • Improved system bar behavior and safe-area handling.
    • Fixed long-running CI jobs by increasing workflow timeouts.
  • Chores

    • Updated Android, iOS, CLI, and core packages to the latest alpha release and refreshed changelogs.

jworreth and others added 30 commits February 17, 2026 15:52
…m#8346)

Co-authored-by: jwo <jonathan.worreth@protonmail.ch>
Co-authored-by: Mark Anderson <mark.anderson@outsystems.com>
Co-authored-by: Steven Sherry <steven.r.sherry@gmail.com>
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
Co-authored-by: Joseph Pender <joey.pender@outsystems.com>
@riderx

riderx commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Claude Code could not resolve this upstream sync completely. Please review the branch carefully before merging.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Beta npm build

Maintainers can publish one Capacitor Plus workspace package from this PR to npm for fast testing.

Comment /publish-beta <package> after the PR checks are green.

Examples:

/publish-beta core
/publish-beta cli
/publish-beta @capacitor-plus/core

If exactly one workspace package changed, /publish-beta without a package will use that package.

Packages:

  • core (@capacitor-plus/core)
  • cli (@capacitor-plus/cli)
  • android (@capacitor-plus/android)
  • ios (@capacitor-plus/ios)

The workflow will:

  • publish a prerelease package on the beta tag
  • add a pinned pr-82 dist-tag for the selected package
  • update this comment with the install command

Security note: beta publish is only enabled for branches inside this repository.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 270bcc0c-ccd7-491c-a861-64e851bde27e

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2895a and cfe72f0.

📒 Files selected for processing (1)
  • ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
📝 Walkthrough

Walkthrough

This PR decouples Cordova support from Capacitor core on Android and iOS into standalone CordovaPlugin implementations using a new generic call-interceptor mechanism, bumps Android/iOS SDK and toolchain versions, refactors CLI iOS Cordova podspec/SPM logic into shared utilities, replaces CLI live-reload flags with a single --url option, updates telemetry endpoints, and refreshes package metadata/changelogs for the 9.0.0-alpha.5 release.

Changes

Cordova Decoupling (Core)

Layer / File(s) Summary
Android interceptor contract and Bridge wiring
android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java, Bridge.java
Adds MessageHandler.Interceptor and Bridge.getCallInterceptor/registerInterceptor, removing direct PluginManager coupling.
Bridge Cordova removal and Builder rewrite
android/capacitor/src/main/java/com/getcapacitor/Bridge.java
Removes Cordova fields/lifecycle delegation; permission, activity, and deploy checks now go through __CordovaPlugin; Builder.create() rewritten.
Android CordovaPlugin module
android/capacitor-cordova/...
New Gradle module with CordovaPlugin.java, manifest, proguard rules, and reduced visibility on mock helper classes.
iOS bridge Cordova removal
ios/Capacitor/Capacitor/CapacitorBridge.swift, CAPBridgeViewController.swift, CAPInstanceDescriptor.*, JSExport.swift, WebViewDelegationHandler.swift, CAPBridgeProtocol.swift
Removes Cordova config parsing/injection, adds RegistrationList, callInterceptors, and registerCallInterceptor.
iOS CordovaPlugin module
ios/CapacitorCordova/.../Plugin.swift, xcodeproj, podspecs
New CordovaPlugin.swift handles JS injection and "cordova" interceptor; removes legacy screen-orientation category and TmpViewController.

Estimated code review effort: 4 (Complex) | ~75 minutes

Toolchain/SDK Bumps

Layer / File(s) Summary
Android Gradle/AGP/SDK bumps
android/*, android-template/*, capacitor-cordova-android-plugins/build.gradle
Bumps AGP to 9.2.1, compileSdk/minSdk/targetSdk, and Gradle wrapper to 9.5.1 with retry config.
gradlew script rewrites
android/gradlew*, android-template/gradlew*
Removes CLASSPATH-based invocation; reworks Windows batch exit-code handling.
iOS deployment target bump
templates, podspecs, xcodeproj files, pods_helpers.rb
Raises IPHONEOS_DEPLOYMENT_TARGET to 16.0 and adopts @main entry point.

CLI Cordova/SPM and Run Command

Layer / File(s) Summary
New cordova-ios.ts utility module
cli/src/util/cordova-ios.ts
Extracts podspec/package generation, native file copying, and Podfile line generation for iOS Cordova plugins.
ios/update.ts and android/update.ts Cordova gating
cli/src/ios/update.ts, cli/src/android/update.ts
Threads enableCordova flag through update pipelines; removes Kotlin auto-detection in Android update.
SPM PackageOptions
cli/src/declarations.ts, cli/src/util/spm.ts
Adds symlink and moduleAliases support for per-plugin SPM configuration.
run --url option
cli/src/index.ts, cli/src/tasks/run.ts, cli/src/util/livereload.ts
Replaces live-reload flags with --url, validates protocol, updates config edits.
Telemetry and config updates
cli/src/ipc.ts, cli/src/telemetry.ts, cli/src/config.ts, cli/test/telemetry.spec.ts
Changes telemetry endpoint/status code, adds ios_package_manager metric, and bumps default minVersions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Metadata & Release

Layer / File(s) Summary
Package metadata/versions
android/package.json, cli/package.json, core/package.json, ios/package.json, lerna.json
Renames packages to @capacitor/* and bumps to 9.0.0-alpha.5.
Changelogs
CHANGELOG.md, android/CHANGELOG.md, cli/CHANGELOG.md, core/CHANGELOG.md, ios/CHANGELOG.md
Adds new alpha release entries and updates compare links.
Migration/CI/publish scripts
cli/src/tasks/migrate.ts, .github/workflows/test.yml, scripts/publish-android.sh
Updates migration version constants, CI timeouts, and Gradle publish task names.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebView
  participant Bridge
  participant CordovaPlugin
  participant PluginManager
  WebView->>Bridge: postMessage(type="cordova", data)
  Bridge->>Bridge: getCallInterceptor("cordova")
  Bridge->>CordovaPlugin: intercept(service, action, callbackId, actionArgs)
  CordovaPlugin->>PluginManager: exec(...)
  PluginManager-->>CordovaPlugin: result
  CordovaPlugin-->>WebView: callback response
Loading
sequenceDiagram
  participant CLI
  participant runCommand
  participant CapLiveReloadHelper
  participant AppConfig
  CLI->>runCommand: run --url http://host:port
  runCommand->>runCommand: validate URL protocol
  runCommand->>CapLiveReloadHelper: editCapConfigForLiveReload(config, platform, url)
  CapLiveReloadHelper->>AppConfig: write server.url to capacitor.config.json
  runCommand-->>CLI: log "listening for: url"
Loading

Related PRs: None identified in the provided context.

Suggested labels: breaking-change, cordova, android, ios, cli, release

Suggested reviewers: None specified.

🐰 Cordova hops away, a plugin now apart,
New interceptors bridge the gap with art,
SDKs climb higher, sixteen for the fold,
Nine-point-oh-alpha, a version bold,
URLs replace the reload of old.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the upstream sync and the main iOS change to remove Cordova.framework.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Review ran into problems

🔥 Problems

Repository analysis: Couldn't refresh Cap-go/capacitor-plus clone - clone failed: Clone operation failed: Stream initialization permanently failed: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java (1)

163-173: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing # delimiter corrupts URLs containing a fragment.

uri.getFragment() returns the fragment value without the leading #. Appending it directly after the query string produces a malformed URL (e.g. ...?foo=bar#section becomes ...?foo=barsection), silently corrupting any request whose URL contains a fragment.

🐛 Proposed fix
             String unEncodedUrlString =
                 uri.getScheme() +
                 "://" +
                 uri.getAuthority() +
                 uri.getPath() +
                 (!urlQuery.equals("") ? "?" + urlQuery : "") +
-                (uri.getFragment() != null ? uri.getFragment() : "");
+                (uri.getFragment() != null ? "#" + uri.getFragment() : "");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java`
around lines 163 - 173, The URL reconstruction in HttpRequestHandler is missing
the fragment delimiter, so URLs with fragments are being concatenated
incorrectly. Update the logic that builds unEncodedUrlString in the request
setup path to append the fragment from uri.getFragment() with a leading # only
when present, keeping the existing scheme, authority, path, and query handling
intact. Use the URL/URI assembly block in HttpRequestHandler to locate the fix.
android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java (1)

176-192: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the API 35 gate around root insets

android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java:176-192ViewCompat.getRootWindowInsets(v) is compat-safe on older Android versions too; this branch sets the safe-area CSS vars to 0 on API 24-34 until the listener runs. Call it unconditionally here, or move the Android 15-specific check to the code that actually needs it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java`
around lines 176 - 192, The safe-area initialization in
SystemBars.initSafeAreaCSSVariables() is unnecessarily gated by the Android 15
check, which forces zero insets on older versions before listeners update.
Remove the API 35-specific branch around ViewCompat.getRootWindowInsets(v) and
fetch root window insets unconditionally here; keep any Android 15-only logic
only in the code path that truly depends on it.
CHANGELOG.md (1)

6-119: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

CHANGELOG.md was manually edited, and merge conflict resolution left duplicated/inconsistent content.

Per repo guidelines, CHANGELOG.md should not be manually edited since it's managed by CI/CD. This diff manually edits it as part of the merge sync, and the result contains clear artifacts of an unresolved conflict:

  • The 9.0.0-alpha.4 header/entry appears twice (Line 14 and Line 37).
  • The entire 9.0.0-alpha.0 "Bug Fixes" section is duplicated verbatim (Lines 26-36 and Lines 61-71).
  • Lines 111-115 mix ionic-team/capacitor compare/issue/commit URLs with Cap-go/capacitor-plus issue/commit URLs within the same entry, leaving a broken/inconsistent changelog history.

This matches the PR's own note that "Claude Code could not fully resolve the upstream sync" and should be cleaned up (or regenerated via the normal CI/CD changelog process) before merging.

As per coding guidelines, "Do not manually edit CHANGELOG.md; it is managed automatically by CI/CD."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 6 - 119, The CHANGELOG content has unresolved
merge artifacts and inconsistent history that need to be removed. Clean up the
duplicated 9.0.0-alpha.4 and 9.0.0-alpha.0 entries, then make the release notes
consistent with the upstream Capacitor history by fixing the mixed
compare/issue/commit references in the 8.3.1/8.3.2 area. Use the existing
changelog section headers and release entries as the anchor points, and ensure
the final file is either regenerated by the normal CI/CD changelog flow or left
in a single, non-duplicated, internally consistent state.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/capacitor/src/main/java/com/getcapacitor/Bridge.java`:
- Around line 293-308: Guard the WebView package lookup in
isMinimumWebViewInstalled() by checking the result of
WebView.getCurrentWebViewPackage() before using it, and also handle a null
versionName safely. If the package info or version string is unavailable, return
false immediately so the version parsing logic only runs when info is present.

In `@android/CHANGELOG.md`:
- Around line 6-84: The android/CHANGELOG.md release history contains duplicate
entries for several versions, which creates conflicting compare links and
repeated release notes. Remove the repeated blocks for 9.0.0-alpha.4,
9.0.0-alpha.3, 9.0.0-alpha.0, 8.4.0, 8.3.4, and 8.3.3 so each release appears
only once, keeping the correct compare link and notes in the remaining canonical
entry.

In `@cli/src/ios/update.ts`:
- Around line 65-88: The `update` flow in `cli/src/ios/update.ts` is recomputing
`getCapacitorPackageVersion` for every plugin even though `iosPlatformVersion`
is independent of `plugin`; hoist that await out of the
`Promise.all`/`validSPMPackages.map` loop and reuse the single value inside the
per-plugin work. While updating the `Package.swift` parsing logic, make sure the
`major(version)` comparison in the `regex`/`content.match` path is guarded so
malformed or missing semver values do not throw during `update`.

In `@cli/src/tasks/migrate.ts`:
- Line 3: The migration task is importing rimraf as a default export, but rimraf
v6 only exposes named exports, so the current sync call will break at runtime.
Update the import in migrate.ts to use the named sync export (for example,
rimrafSync) and change the existing rimraf.sync(...) usage in the migration
logic to call that named function instead.

In `@cli/src/util/cordova-ios.ts`:
- Around line 299-318: In cordovaPodfileLines, remove the unnecessary async from
the cordovaPlugins.map callback because it does not await anything and the logic
is synchronous. Update the loop in cordova-ios.ts so the callback used to walk
cordovaPlugins, getPlatformElement, and podspec/pods/pod stays plain synchronous
code, preserving the current pods accumulation without implying async behavior.
- Around line 340-344: generateCordovaPackageFiles is returning before the async
per-plugin work completes because the cordovaPlugins.map callback does not await
generateCordovaPackageFile. Update generateCordovaPackageFiles to wait for each
generateCordovaPackageFile call to finish, so callers like ios/update.ts do not
continue into checkPluginsForPackageSwift and generatePackageFile until all
Package.swift files are written. Use the generateCordovaPackageFiles and
generateCordovaPackageFile symbols to locate the fix.
- Around line 22-27: The async podspec writes in generateCordovaPodspecs are not
awaited, so the function can finish before the .podspec files are written and
errors may be unhandled. Update generateCordovaPodspecs in cordova-ios.ts to
await both generateCordovaPodspec calls (or run them through a Promise-based
concurrency pattern) so the plugin podspec generation completes before
installCocoaPodsPlugins continues.

In `@ios/Capacitor/Capacitor/CAPBridgeProtocol.swift`:
- Around line 82-84: The new CAPBridgeProtocol requirement
registerCallInterceptor(_:handler:) is source-breaking for existing conformers.
Update the protocol so this API is not required for all adopters, either by
moving the behavior into a protocol extension with a default implementation or
by making the interceptor registration optional in CAPBridgeProtocol while
keeping CAPBridge-related call interception functionality available.

In `@ios/Capacitor/Capacitor/CAPBridgeViewController.swift`:
- Around line 94-110: In CAPBridgeViewController.updateAppLocationIfNeeded, stop
using perform(_:) on __CordovaPlugin for cordovaDeployDisabled because it is an
`@objc` Bool and the current call can misread the scalar value. Read the property
through KVC or a typed protocol/cast on the plugin instance instead, then keep
the existing conditional logic using cordovaDeployDisabled unchanged.

In `@ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift`:
- Line 43: The cast in Plugin should use a concrete array element type instead
of the untyped Array, since `as? Array` will not compile. Update the `args`
assignment in `Plugin` to cast to `[Any]` (or the correct known element type) so
the `actionArgs` value can be read safely.

---

Outside diff comments:
In `@android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java`:
- Around line 176-192: The safe-area initialization in
SystemBars.initSafeAreaCSSVariables() is unnecessarily gated by the Android 15
check, which forces zero insets on older versions before listeners update.
Remove the API 35-specific branch around ViewCompat.getRootWindowInsets(v) and
fetch root window insets unconditionally here; keep any Android 15-only logic
only in the code path that truly depends on it.

In
`@android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java`:
- Around line 163-173: The URL reconstruction in HttpRequestHandler is missing
the fragment delimiter, so URLs with fragments are being concatenated
incorrectly. Update the logic that builds unEncodedUrlString in the request
setup path to append the fragment from uri.getFragment() with a leading # only
when present, keeping the existing scheme, authority, path, and query handling
intact. Use the URL/URI assembly block in HttpRequestHandler to locate the fix.

In `@CHANGELOG.md`:
- Around line 6-119: The CHANGELOG content has unresolved merge artifacts and
inconsistent history that need to be removed. Clean up the duplicated
9.0.0-alpha.4 and 9.0.0-alpha.0 entries, then make the release notes consistent
with the upstream Capacitor history by fixing the mixed compare/issue/commit
references in the 8.3.1/8.3.2 area. Use the existing changelog section headers
and release entries as the anchor points, and ensure the final file is either
regenerated by the normal CI/CD changelog flow or left in a single,
non-duplicated, internally consistent state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b6e7d24-8d64-4d89-832d-0d2cae0f09f9

📥 Commits

Reviewing files that changed from the base of the PR and between 51a1030 and 2f2895a.

⛔ Files ignored due to path filters (2)
  • android-template/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (87)
  • .github/workflows/test.yml
  • CHANGELOG.md
  • android-template/app/build.gradle
  • android-template/build.gradle
  • android-template/gradle/wrapper/gradle-wrapper.properties
  • android-template/gradlew
  • android-template/gradlew.bat
  • android-template/settings.gradle
  • android-template/variables.gradle
  • android/CHANGELOG.md
  • android/capacitor-cordova/build.gradle
  • android/capacitor-cordova/proguard-rules.pro
  • android/capacitor-cordova/src/main/AndroidManifest.xml
  • android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java
  • android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
  • android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
  • android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
  • android/capacitor/build.gradle
  • android/capacitor/proguard-rules.pro
  • android/capacitor/src/main/java/com/getcapacitor/Bridge.java
  • android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
  • android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
  • android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
  • android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
  • android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java
  • android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookieManager.java
  • android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
  • android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
  • android/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java
  • android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java
  • android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java
  • android/gradle/wrapper/gradle-wrapper.properties
  • android/gradlew
  • android/gradlew.bat
  • android/package.json
  • android/settings.gradle
  • capacitor-cordova-android-plugins/build.gradle
  • cli/CHANGELOG.md
  • cli/package.json
  • cli/src/android/update.ts
  • cli/src/config.ts
  • cli/src/declarations.ts
  • cli/src/index.ts
  • cli/src/ios/update.ts
  • cli/src/ipc.ts
  • cli/src/tasks/migrate.ts
  • cli/src/tasks/run.ts
  • cli/src/telemetry.ts
  • cli/src/util/cordova-ios.ts
  • cli/src/util/iosplugin.ts
  • cli/src/util/livereload.ts
  • cli/src/util/spm.ts
  • cli/test/telemetry.spec.ts
  • core/CHANGELOG.md
  • core/package.json
  • ios-pods-template/App/App.xcodeproj/project.pbxproj
  • ios-pods-template/App/App/AppDelegate.swift
  • ios-pods-template/App/Podfile
  • ios-spm-template/App/App.xcodeproj/project.pbxproj
  • ios-spm-template/App/App/AppDelegate.swift
  • ios-spm-template/App/CapApp-SPM/Package.swift
  • ios/CHANGELOG.md
  • ios/Capacitor.podspec
  • ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
  • ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
  • ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
  • ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h
  • ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m
  • ios/Capacitor/Capacitor/CAPBridgeViewController.swift
  • ios/Capacitor/Capacitor/CAPInstanceDescriptor.h
  • ios/Capacitor/Capacitor/CAPInstanceDescriptor.m
  • ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift
  • ios/Capacitor/Capacitor/CapacitorBridge.swift
  • ios/Capacitor/Capacitor/JS.swift
  • ios/Capacitor/Capacitor/JSExport.swift
  • ios/Capacitor/Capacitor/PluginConfig.swift
  • ios/Capacitor/Capacitor/TmpViewController.swift
  • ios/Capacitor/Capacitor/WebViewDelegationHandler.swift
  • ios/Capacitor/CapacitorTests/ConfigurationTests.swift
  • ios/Capacitor/CapacitorTests/PluginCallAccessorTests.m
  • ios/CapacitorCordova.podspec
  • ios/CapacitorCordova/CapacitorCordova.xcodeproj/project.pbxproj
  • ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift
  • ios/package.json
  • ios/scripts/pods_helpers.rb
  • lerna.json
  • scripts/publish-android.sh
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)
💤 Files with no reviewable changes (10)
  • ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h
  • ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m
  • ios/Capacitor/Capacitor/JSExport.swift
  • android-template/settings.gradle
  • ios/Capacitor/Capacitor/CAPInstanceDescriptor.m
  • android-template/app/build.gradle
  • ios/Capacitor/CapacitorTests/ConfigurationTests.swift
  • ios/Capacitor/Capacitor/TmpViewController.swift
  • ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
  • android/capacitor/proguard-rules.pro

Comment on lines 293 to 308
@SuppressLint("WebViewApiAvailability")
public boolean isMinimumWebViewInstalled() {
PackageManager pm = getContext().getPackageManager();

// Check getCurrentWebViewPackage() directly if above Android 8
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PackageInfo info = WebView.getCurrentWebViewPackage();
Pattern pattern = Pattern.compile("(\\d+)");
Matcher matcher = pattern.matcher(info.versionName);
if (matcher.find()) {
String majorVersionStr = matcher.group(0);
int majorVersion = Integer.parseInt(majorVersionStr);
if (info.packageName.equals("com.huawei.webview")) {
return majorVersion >= config.getMinHuaweiWebViewVersion();
}
return majorVersion >= config.getMinWebViewVersion();
} else {
return false;
}
}

// Otherwise manually check WebView versions
try {
PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.chrome");
String majorVersionStr = info.versionName.split("\\.")[0];
int majorVersion = Integer.parseInt(majorVersionStr);
return majorVersion >= config.getMinWebViewVersion();
} catch (Exception ex) {
Logger.warn("Unable to get package info for 'com.google.android.webview'" + ex.toString());
}

try {
PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.webview");
String majorVersionStr = info.versionName.split("\\.")[0];
PackageInfo info = WebView.getCurrentWebViewPackage();
Pattern pattern = Pattern.compile("(\\d+)");
Matcher matcher = pattern.matcher(info.versionName);
if (matcher.find()) {
String majorVersionStr = matcher.group(0);
int majorVersion = Integer.parseInt(majorVersionStr);
if (info.packageName.equals("com.huawei.webview")) {
return majorVersion >= config.getMinHuaweiWebViewVersion();
}
return majorVersion >= config.getMinWebViewVersion();
} catch (Exception ex) {
Logger.warn("Unable to get package info for 'com.android.webview'" + ex.toString());
}

final int amazonFireMajorWebViewVersion = extractWebViewMajorVersion(pm, "com.amazon.webview.chromium");
if (amazonFireMajorWebViewVersion >= config.getMinWebViewVersion()) {
return true;
}

// Could not detect any webview, return false
return false;
}

private int extractWebViewMajorVersion(final PackageManager pm, final String webViewPackageName) {
try {
final PackageInfo info = InternalUtils.getPackageInfo(pm, webViewPackageName);
final String majorVersionStr = info.versionName.split("\\.")[0];
final int majorVersion = Integer.parseInt(majorVersionStr);
return majorVersion;
} catch (Exception ex) {
Logger.warn(String.format("Unable to get package info for '%s' with err '%s'", webViewPackageName, ex));
} else {
return false;
}
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target method and nearby constructor/load flow.
sed -n '250,340p' android/capacitor/src/main/java/com/getcapacitor/Bridge.java

printf '\n--- constructor/load flow ---\n'
sed -n '170,230p' android/capacitor/src/main/java/com/getcapacitor/Bridge.java

printf '\n--- search for current WebView package usage ---\n'
rg -n "getCurrentWebViewPackage|versionName|isMinimumWebViewInstalled" android/capacitor/src/main/java -S

printf '\n--- search for previous implementation or related guards ---\n'
git diff -- android/capacitor/src/main/java/com/getcapacitor/Bridge.java | sed -n '1,220p'

Repository: Cap-go/capacitor-plus

Length of output: 1955


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

# Query Android documentation for nullability of WebView.getCurrentWebViewPackage()
python3 - <<'PY'
import urllib.request, urllib.parse, re
queries = [
    "Android WebView.getCurrentWebViewPackage nullable PackageInfo versionName null",
]
for q in queries:
    url = "https://www.google.com/search?q=" + urllib.parse.quote(q)
    print("QUERY:", q)
    try:
        with urllib.request.urlopen(url, timeout=10) as r:
            html = r.read().decode("utf-8", "ignore")
        # very light extraction
        for m in re.finditer(r"getCurrentWebViewPackage|PackageInfo|versionName|nullable", html, re.I):
            start = max(0, m.start()-120)
            end = min(len(html), m.end()+160)
            print(html[start:end].replace("\n", " ")[:280])
            print("---")
            break
    except Exception as e:
        print("ERR:", e)
PY

Length of output: 146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any existing null handling or assumptions around WebView package info.
rg -n "WebView\.getCurrentWebViewPackage|PackageInfo info = WebView|getMinHuaweiWebViewVersion|getMinWebViewVersion" android -S

# Show the exact method with line numbers.
nl -ba android/capacitor/src/main/java/com/getcapacitor/Bridge.java | sed -n '292,312p'

Repository: Cap-go/capacitor-plus

Length of output: 1955


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the Android minSdk to see whether the WebView API call needs a version gate.
rg -n "minSdkVersion|minSdk" android -S

# Inspect the manifest / gradle file locations likely to define it.
fd -a "build.gradle*" android
fd -a "gradle.properties" android
fd -a "AndroidManifest.xml" android/capacitor/src/main

Repository: Cap-go/capacitor-plus

Length of output: 1955


Guard the WebView package lookup. WebView.getCurrentWebViewPackage() and info.versionName can be null here, which will crash before the fallback runs. Return false when package info isn’t available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/capacitor/src/main/java/com/getcapacitor/Bridge.java` around lines
293 - 308, Guard the WebView package lookup in isMinimumWebViewInstalled() by
checking the result of WebView.getCurrentWebViewPackage() before using it, and
also handle a null versionName safely. If the package info or version string is
unavailable, return false immediately so the version parsing logic only runs
when info is present.

Comment thread android/CHANGELOG.md Outdated
Comment thread cli/src/ios/update.ts Outdated
Comment on lines +65 to +88
await Promise.all(
validSPMPackages.map(async (plugin) => {
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
const packageSwiftPath = join(plugin.rootPath, 'Package.swift');
let content = await readFile(packageSwiftPath, { encoding: 'utf-8' });
const regex = new RegExp(
'url:\\s*"https://github.com/ionic-team/capacitor-swift-pm\\.git",\\s*from:\\s*"([^"]+)"',
);
const version = content.match(regex)?.[1];
const majorCapVersion = major(iosPlatformVersion);
if (version && major(version) != majorCapVersion) {
const preCapVersion = prerelease(iosPlatformVersion);
const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`;
content = setAllStringIn(
content,
`url: "https://github.com/ionic-team/capacitor-swift-pm.git",`,
`)`,
` from: "${forceVersion}"`,
);
await writeFile(packageSwiftPath, content);
logger.warn(`${plugin.id} is built for Capacitor ${major(version)}, it might cause issues`);
}
}),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist getCapacitorPackageVersion out of the per-plugin loop.

iosPlatformVersion does not depend on plugin, yet it is awaited once per package inside the Promise.all map. Compute it once before the loop. Also note major(version) will throw if the regex-captured version is not valid semver; consider guarding if plugin Package.swift contents can be malformed.

♻️ Proposed change
     const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
+    const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
     await Promise.all(
       validSPMPackages.map(async (plugin) => {
-        const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
         const packageSwiftPath = join(plugin.rootPath, 'Package.swift');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await Promise.all(
validSPMPackages.map(async (plugin) => {
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
const packageSwiftPath = join(plugin.rootPath, 'Package.swift');
let content = await readFile(packageSwiftPath, { encoding: 'utf-8' });
const regex = new RegExp(
'url:\\s*"https://github.com/ionic-team/capacitor-swift-pm\\.git",\\s*from:\\s*"([^"]+)"',
);
const version = content.match(regex)?.[1];
const majorCapVersion = major(iosPlatformVersion);
if (version && major(version) != majorCapVersion) {
const preCapVersion = prerelease(iosPlatformVersion);
const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`;
content = setAllStringIn(
content,
`url: "https://github.com/ionic-team/capacitor-swift-pm.git",`,
`)`,
` from: "${forceVersion}"`,
);
await writeFile(packageSwiftPath, content);
logger.warn(`${plugin.id} is built for Capacitor ${major(version)}, it might cause issues`);
}
}),
);
const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
await Promise.all(
validSPMPackages.map(async (plugin) => {
const packageSwiftPath = join(plugin.rootPath, 'Package.swift');
let content = await readFile(packageSwiftPath, { encoding: 'utf-8' });
const regex = new RegExp(
'url:\\s*"https://github.com/ionic-team/capacitor-swift-pm\\.git",\\s*from:\\s*"([^"]+)"',
);
const version = content.match(regex)?.[1];
const majorCapVersion = major(iosPlatformVersion);
if (version && major(version) != majorCapVersion) {
const preCapVersion = prerelease(iosPlatformVersion);
const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`;
content = setAllStringIn(
content,
`url: "https://github.com/ionic-team/capacitor-swift-pm.git",`,
`)`,
` from: "${forceVersion}"`,
);
await writeFile(packageSwiftPath, content);
logger.warn(`${plugin.id} is built for Capacitor ${major(version)}, it might cause issues`);
}
}),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/ios/update.ts` around lines 65 - 88, The `update` flow in
`cli/src/ios/update.ts` is recomputing `getCapacitorPackageVersion` for every
plugin even though `iosPlatformVersion` is independent of `plugin`; hoist that
await out of the `Promise.all`/`validSPMPackages.map` loop and reuse the single
value inside the per-plugin work. While updating the `Package.swift` parsing
logic, make sure the `major(version)` comparison in the `regex`/`content.match`
path is guarded so malformed or missing semver values do not throw during
`update`.

Comment thread cli/src/tasks/migrate.ts Outdated
import { writeFileSync, readFileSync, existsSync } from 'fs-extra';
import { join } from 'path';
import { rimraf } from 'rimraf';
import rimraf from 'rimraf';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package version references =="
rg -n '"rimraf"|rimraf@|from '\''rimraf'\''|from \"rimraf\"' cli package.json pnpm-lock.yaml package-lock.json yarn.lock --no-messages || true

echo
echo "== migrate.ts context =="
sed -n '1,380p' cli/src/tasks/migrate.ts

Repository: Cap-go/capacitor-plus

Length of output: 1955


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate rimraf package metadata if present =="
fd -a "package.json" . | rg "rimraf|cli/package.json|package.json$" || true

echo
echo "== search for rimraf usages =="
rg -n "\brimraf\b" . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' || true

echo
echo "== inspect relevant package file(s) =="
for f in cli/package.json package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,260p' "$f"
  fi
done

Repository: Cap-go/capacitor-plus

Length of output: 1955


🌐 Web query:

rimraf v6 default export named exports rimrafSync sync import documentation

💡 Result:

In rimraf v6, there is no default export [1][2]. You must use named imports to access the library's functions [1][3]. To use the synchronous version of rimraf, import rimrafSync from the package [1][2]: import { rimrafSync } from 'rimraf'; You can also import other available functions, such as the primary async rimraf function or native implementations, using the same named export pattern [3][2]: import { rimraf, rimrafSync, native, nativeSync } from 'rimraf'; If you are using CommonJS, use destructuring assignment to access these named exports [3][2]: const { rimraf, rimrafSync } = require('rimraf');

Citations:


🌐 Web query:

site:npmjs.com rimraf 6.0.1 exports rimrafSync default export

💡 Result:

In rimraf version 6.0.1 and later, there is no default export [1]. You must import the functions directly using named imports [1]. For example, to use rimrafSync, you should use the following syntax: import { rimrafSync } from 'rimraf'; [1] Alternatively, if you are using CommonJS, you can import the functions from the package as follows: const { rimrafSync } = require('rimraf');

Citations:


Switch to the named rimraf export. rimraf v6 has no default export, so rimraf.sync(...) will fail at runtime; import rimrafSync and call that instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/tasks/migrate.ts` at line 3, The migration task is importing rimraf
as a default export, but rimraf v6 only exposes named exports, so the current
sync call will break at runtime. Update the import in migrate.ts to use the
named sync export (for example, rimrafSync) and change the existing
rimraf.sync(...) usage in the migration logic to call that named function
instead.

Comment thread cli/src/util/cordova-ios.ts Outdated
Comment on lines +22 to +27
export async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config): Promise<void> {
const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
generateCordovaPodspec(noStaticPlugins, config, false);
generateCordovaPodspec(staticPlugins, config, true);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Await the async podspec generation calls.

generateCordovaPodspec is async and performs writeFile, but both invocations are fire-and-forget. generateCordovaPodspecs resolves before the .podspec files are actually written, so the downstream pod install in installCocoaPodsPlugins can race against unfinished writes (and any write error becomes an unhandled rejection).

🐛 Proposed fix
 export async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config): Promise<void> {
   const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
   const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
-  generateCordovaPodspec(noStaticPlugins, config, false);
-  generateCordovaPodspec(staticPlugins, config, true);
+  await Promise.all([
+    generateCordovaPodspec(noStaticPlugins, config, false),
+    generateCordovaPodspec(staticPlugins, config, true),
+  ]);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config): Promise<void> {
const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
generateCordovaPodspec(noStaticPlugins, config, false);
generateCordovaPodspec(staticPlugins, config, true);
}
export async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config): Promise<void> {
const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
await Promise.all([
generateCordovaPodspec(noStaticPlugins, config, false),
generateCordovaPodspec(staticPlugins, config, true),
]);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/util/cordova-ios.ts` around lines 22 - 27, The async podspec writes
in generateCordovaPodspecs are not awaited, so the function can finish before
the .podspec files are written and errors may be unhandled. Update
generateCordovaPodspecs in cordova-ios.ts to await both generateCordovaPodspec
calls (or run them through a Promise-based concurrency pattern) so the plugin
podspec generation completes before installCocoaPodsPlugins continues.

Comment thread cli/src/util/cordova-ios.ts Outdated
Comment on lines +299 to +318
cordovaPlugins.map(async (p) => {
const podspecs = getPlatformElement(p, platform, 'podspec');
podspecs.map((podspec: any) => {
podspec.pods.map((pPods: any) => {
pPods.pod.map((pod: any) => {
if (pod.$.git) {
let gitRef = '';
if (pod.$.tag) {
gitRef = `, :tag => '${pod.$.tag}'`;
} else if (pod.$.branch) {
gitRef = `, :branch => '${pod.$.branch}'`;
} else if (pod.$.commit) {
gitRef = `, :commit => '${pod.$.commit}'`;
}
pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
}
});
});
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unnecessary async callback in cordovaPodfileLines.

The .map(async (p) => {...}) body contains no await, so it currently runs synchronously and pods is populated before the function returns. But marking it async is misleading and will silently break the synchronous pods accumulation if anyone later adds an await inside. Since this function is synchronous, use a plain callback.

♻️ Proposed change
-  cordovaPlugins.map(async (p) => {
+  cordovaPlugins.map((p) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cordovaPlugins.map(async (p) => {
const podspecs = getPlatformElement(p, platform, 'podspec');
podspecs.map((podspec: any) => {
podspec.pods.map((pPods: any) => {
pPods.pod.map((pod: any) => {
if (pod.$.git) {
let gitRef = '';
if (pod.$.tag) {
gitRef = `, :tag => '${pod.$.tag}'`;
} else if (pod.$.branch) {
gitRef = `, :branch => '${pod.$.branch}'`;
} else if (pod.$.commit) {
gitRef = `, :commit => '${pod.$.commit}'`;
}
pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
}
});
});
});
});
cordovaPlugins.map((p) => {
const podspecs = getPlatformElement(p, platform, 'podspec');
podspecs.map((podspec: any) => {
podspec.pods.map((pPods: any) => {
pPods.pod.map((pod: any) => {
if (pod.$.git) {
let gitRef = '';
if (pod.$.tag) {
gitRef = `, :tag => '${pod.$.tag}'`;
} else if (pod.$.branch) {
gitRef = `, :branch => '${pod.$.branch}'`;
} else if (pod.$.commit) {
gitRef = `, :commit => '${pod.$.commit}'`;
}
pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
}
});
});
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/util/cordova-ios.ts` around lines 299 - 318, In cordovaPodfileLines,
remove the unnecessary async from the cordovaPlugins.map callback because it
does not await anything and the logic is synchronous. Update the loop in
cordova-ios.ts so the callback used to walk cordovaPlugins, getPlatformElement,
and podspec/pods/pod stays plain synchronous code, preserving the current pods
accumulation without implying async behavior.

Comment thread cli/src/util/cordova-ios.ts Outdated
Comment on lines +340 to +344
export async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config): Promise<void> {
cordovaPlugins.map((plugin: any) => {
generateCordovaPackageFile(plugin, config);
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Await per-plugin package file generation.

generateCordovaPackageFile is async and writes Package.swift, but the .map callbacks are never awaited, so generateCordovaPackageFiles resolves before any file is written. The caller in ios/update.ts (Line 61) proceeds to checkPluginsForPackageSwift/generatePackageFile, which can observe missing or partially written files.

🐛 Proposed fix
 export async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config): Promise<void> {
-  cordovaPlugins.map((plugin: any) => {
-    generateCordovaPackageFile(plugin, config);
-  });
+  await Promise.all(cordovaPlugins.map((plugin: Plugin) => generateCordovaPackageFile(plugin, config)));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config): Promise<void> {
cordovaPlugins.map((plugin: any) => {
generateCordovaPackageFile(plugin, config);
});
}
export async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config): Promise<void> {
await Promise.all(cordovaPlugins.map((plugin: Plugin) => generateCordovaPackageFile(plugin, config)));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/util/cordova-ios.ts` around lines 340 - 344,
generateCordovaPackageFiles is returning before the async per-plugin work
completes because the cordovaPlugins.map callback does not await
generateCordovaPackageFile. Update generateCordovaPackageFiles to wait for each
generateCordovaPackageFile call to finish, so callers like ios/update.ts do not
continue into checkPluginsForPackageSwift and generatePackageFile until all
Package.swift files are written. Use the generateCordovaPackageFiles and
generateCordovaPackageFile symbols to locate the fix.

Comment thread ios/Capacitor/Capacitor/CAPBridgeProtocol.swift Outdated
Comment thread ios/Capacitor/Capacitor/CAPBridgeViewController.swift Outdated
let method = dict["action"] as? String ?? ""
let callbackId = dict["callbackId"] as? String ?? ""

let args = dict["actionArgs"] as? Array ?? []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Swift conditional cast as? Array without element type compile error generic parameter Element

💡 Result:

In Swift, attempting to use the conditional cast operator as? on an Array without specifying the element type—such as writing myArray as? Array or myArray as? [Any] (where the generic structure itself is the target)—will result in a compile-time error because Array is a generic type that requires its Element parameter to be defined [1][2]. The syntax Array or [Element] is not a concrete type in Swift; it is a generic type constructor [3]. To perform a conditional cast, the compiler must know exactly what type of elements you are attempting to cast into, as the runtime performs an element-by-element conversion [1][2]. Key reasons for this behavior include: 1. Mandatory Generic Parameters: Swift collections like Array<Element>, Set<Element>, and Dictionary<Key, Value> cannot exist without their generic parameters [3][2]. Attempting to use them without these parameters creates an incomplete type definition, which the compiler rejects [3]. 2. Element-wise Casting Mechanism: The as? operator for arrays is not a simple pointer cast. The Swift runtime treats an array cast as an element-by-element transformation [1][4]. It effectively iterates through the source array, attempts to cast each individual element to the target Element type, and constructs a new array if all elements succeed [1][2]. Because this logic requires knowledge of the target Element type to perform the cast, you must provide it explicitly [3]. If you are dealing with arrays of unknown types (e.g., [Any]), you must cast to a specific concrete array type, such as myArray as? [String] or myArray as? [Int], so the compiler and runtime understand the target type for the individual elements [1][2]. If the element type is truly unknown at compile time, you cannot perform this type of downcast, as the generic requirement cannot be satisfied [3]. Note that empty arrays can sometimes exhibit "magic" behavior where they appear to cast successfully to any array type [5][6]. This occurs because there are no elements to inspect, but the underlying type requirement remains the same; you should avoid relying on this behavior for logic control, as it can lead to unexpected results or crashes when the array is later populated [7][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the reported line.
sed -n '1,140p' ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift | nl -ba

# Search for similar casts in the repo for context.
rg -n --no-heading 'as\?\s*Array\b|as\?\s*\[[^]]+\]' ios -g '*.swift' || true

Repository: Cap-go/capacitor-plus

Length of output: 1955


🌐 Web query:

Swift let args = dict["actionArgs"] as? Array ?? [] compile error generic parameter Element could not be inferred

💡 Result:

The compile error "Generic parameter 'Element' could not be inferred" occurs because Array in Swift is a generic type that must know what kind of elements it contains [1][2]. When you use as? Array, you are attempting to cast to a non-specific array type without defining its Element type [3][1]. The compiler cannot infer this type from the context, so it requires you to be explicit [4][1]. To fix this, provide the specific type the array is expected to hold [1]. If you expect an array of strings, for example, use: let args = dict["actionArgs"] as? [String]?? [] If you do not know the exact type but know it contains objects that can be treated as generic items, use [Any]: let args = dict["actionArgs"] as? [Any]?? [] The syntax [Type] is shorthand for Array and is generally preferred in Swift [3][5][2]. By specifying the type, you provide the compiler with the necessary information to satisfy the generic parameter requirement [1][6].

Citations:


Specify the array element type in ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift:43 Array needs a concrete Element; as? Array won’t compile. Use [Any] here, or the actual element type if it’s known.

Fix
-            let args = dict["actionArgs"] as? Array ?? []
+            let args = dict["actionArgs"] as? [Any] ?? []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let args = dict["actionArgs"] as? Array ?? []
let args = dict["actionArgs"] as? [Any] ?? []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift` at line
43, The cast in Plugin should use a concrete array element type instead of the
untyped Array, since `as? Array` will not compile. Update the `args` assignment
in `Plugin` to cast to `[Any]` (or the correct known element type) so the
`actionArgs` value can be read safely.

@RobinWitch RobinWitch left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This upstream sync regresses the fork's package identity, which will break beta and release publishing for Capacitor Plus.

The PR changes all workspace package names from the fork namespace back to upstream names:

core/package.json     @capacitor-plus/core    -> @capacitor/core
cli/package.json      @capacitor-plus/cli     -> @capacitor/cli
android/package.json  @capacitor-plus/android -> @capacitor/android
ios/package.json      @capacitor-plus/ios     -> @capacitor/ios

That is not just metadata. The PR beta publish workflow reads pkg.name from the selected workspace (.github/workflows/pr_beta_publish.yml lines 226-233 and 293), then runs npm publish in that package directory and adds a dist-tag using steps.version.outputs.package_name (lines 305-316). With these names, /publish-beta core would try to publish/tag @capacitor/core instead of @capacitor-plus/core, and the documented /publish-beta @capacitor-plus/core selector no longer matches any package name.

Please keep the @capacitor-plus/* package names while syncing the upstream code, otherwise the fork's beta publish path and downstream install commands point at the wrong npm namespace.

cursoragent and others added 2 commits August 13, 2026 00:57
The sync branch had pulled in a large slice of upstream history with an
upstream-preferred conflict resolution, which also reverted the fork's
package metadata. Reset the tree to the plus tip and re-apply only this
PR's own upstream commits.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Rebuilt, but blocked: this is a Capacitor 9 change

What I did: the branch was cut from upstream next with an "upstream-preferred fallback" resolution, so its diff against plus was 89 files / +1748 / −2224, including renaming @capacitor-plus/* back to @capacitor/* and wiping the fork's CHANGELOG.md history. I merged the current plus tip, reset the tree to it, and cherry-picked only upstream commit e5bb94a with original authorship. The diff is now two removed lines in ios/Capacitor/Capacitor.xcodeproj/project.pbxproj.

Why it is still blocked: test-ios fails to build.

CAPInstanceDescriptor.h:5:9: fatal error: module 'Cordova' not found
CAPBridgeViewController.swift:3:8: error: Unable to find module dependency: 'Cordova'

Upstream ionic-team/capacitor#8524 was merged into upstream's next branch (Capacitor 9), where the Cordova decoupling work had already removed those import Cordova statements. On plus, which is Capacitor 8, they are still there, so dropping Cordova.framework from the Xcode project breaks the build.

This belongs to the same bucket as #81: land it as part of a deliberate plus → Capacitor 9 migration, not on the Capacitor 8 line. Leaving it open as a marker for that work.

Posted by an AI agent (Cursor).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.