Skip to content

chore: sync upstream PR #8511 - fix(http): make HttpResponse data type generic#85

Open
riderx wants to merge 23 commits into
plusfrom
sync/upstream-pr-8511
Open

chore: sync upstream PR #8511 - fix(http): make HttpResponse data type generic#85
riderx wants to merge 23 commits into
plusfrom
sync/upstream-pr-8511

Conversation

@riderx

@riderx riderx commented Jul 12, 2026

Copy link
Copy Markdown
Member

Merge Conflict Resolution Required

The sync of upstream PR ionic-team#8511 from @medbenmakhlouf encountered merge conflicts.

Original PR: ionic-team#8511

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 Swift Package Manager options for plugin symlinks and module aliases.
    • Added support for reading decimal configuration values on Android and iOS.
    • Improved HTTP response typing with generic response data.
    • Added iOS package-manager telemetry reporting.
  • Bug Fixes

    • Improved Android system-bar visibility and safe-area inset handling.
    • Fixed cleanup of live-reload configuration after command errors.
    • Improved compatibility for prerelease and plugin Swift Package Manager dependencies.
  • Documentation

    • Updated release notes and API examples for version 8.4.1.

Github Workflow (on behalf of markemer) and others added 23 commits May 7, 2026 16:55
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
Co-authored-by: Joey Pender <joey.pender@outsystems.com>
Co-authored-by: Pedro Bilro <pedro.gustavo.bilro@outsystems.com>
Co-authored-by: Mark Anderson <mark.anderson@outsystems.com>
…#8476)

Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
…am#8492)

Co-authored-by: Mark Anderson <mark.anderson@outsystems.com>
@riderx

riderx commented Jul 12, 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

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-85 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 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

CLI features and runtime handling

Layer / File(s) Summary
SwiftPM package options and plugin updates
cli/src/declarations.ts, cli/src/ios/update.ts, cli/src/util/spm.ts
Adds per-plugin SwiftPM symlink and module-alias options, corrects incompatible package versions, and extends SPM cleanup and copying behavior.
Telemetry and command cleanup
cli/src/ipc.ts, cli/src/tasks/run.ts, cli/src/telemetry.ts, cli/test/telemetry.spec.ts
Updates telemetry submission, records the iOS package manager, restores live-reload configuration on errors, and tests package-manager detection.

Android runtime behavior

Layer / File(s) Summary
Android configuration accessors
android/capacitor/src/main/java/com/getcapacitor/{PluginConfig.java,util/JSONUtils.java}
Adds defaulted double-value accessors for plugin configuration and nested JSON values.
System bar inset modes and visibility
android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java, android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java, android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
Adds validated CSS/disable inset modes, gates inset processing, refactors bar visibility selection, and adds visibility tests.

Public APIs and release packaging

Layer / File(s) Summary
Cross-platform public API updates
ios/Capacitor/Capacitor/PluginConfig.swift, core/src/core-plugins.ts, core/http.md
Adds the iOS double accessor and makes HttpResponse data generic in code and documentation.
Package identities and versions
android/package.json, cli/package.json, core/package.json, ios/package.json, lerna.json
Renames packages to the Capacitor scope, updates package metadata and peer dependencies, and sets version 8.4.1.
Release notes and CI limits
CHANGELOG.md, android/CHANGELOG.md, cli/CHANGELOG.md, core/CHANGELOG.md, ios/CHANGELOG.md, .github/workflows/test.yml
Adds or refreshes 8.4.x release notes and increases selected workflow job timeouts to 30 minutes.

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

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant SwiftPMGenerator
  participant PackageSwift
  participant SymlinkDirectory
  CLI->>SwiftPMGenerator: read packageOptions
  SwiftPMGenerator->>SymlinkDirectory: create optional plugin symlink
  SwiftPMGenerator->>PackageSwift: generate dependency and alias entries
  CLI->>PackageSwift: patch incompatible Capacitor version
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title correctly reflects the HttpResponse generic change and the upstream sync context, which matches a major part of the PR.
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

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

@cursor cursor 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.

Risk: medium. Cursor Bugbot was not present on this PR, so that signal was skipped. This upstream sync touches core/android/ios/cli with bounded changes and added tests; approving without additional reviewers.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@cursor cursor 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.

Risk: medium. This multi-platform upstream sync exceeds the low-risk approval threshold, and lint CI failed with Prettier formatting errors. Human review is required; assigned jcesarmobile and OS-ruimoreiramendes.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@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: 13

🤖 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 @.github/workflows/test.yml:
- Line 33: Restore the timeout-minutes values in all six affected jobs of the
test workflow to 10, preserving the release-triggered workflow contract
documented by the caller. Update each occurrence at the referenced job
definitions and do not leave any at 30 minutes.

In `@android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java`:
- Line 58: Normalize configuredInsetsHandling case-insensitively before
comparing it with INSETS_HANDLING_CSS and INSETS_HANDLING_DISABLE, matching the
existing style handling in SystemBars. Update the logic around the
insetsHandling configuration and its comparisons so values such as “CSS” and
“Disable” select their intended modes while preserving the existing fallback
warning for unsupported values.

In `@android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java`:
- Around line 22-72: Extend SystemBarsTest beyond the current show-only coverage
by parameterizing invokeSetHidden to accept the hide flag and adding assertions
for hide=true that verify controller.hide calls for system, status, and
navigation bars. Add initSystemBars tests covering valid css and disable
insetsHandling values, plus an unknown value that verifies warning logging and
fallback to css.

In `@android/CHANGELOG.md`:
- Around line 10-12: Update the generated changelog heading hierarchy around the
8.4.0 release heading so its “Bug Fixes” subsection uses the immediate child
heading level instead of skipping level 2. Preserve the existing release heading
and apply the same consistent hierarchy to generated changelog sections.

In `@CHANGELOG.md`:
- Around line 13-15: Update the 8.4.0 release heading in the changelog to level
2, or consistently change its subsection headings to level 2, so the heading
hierarchy does not skip a level and satisfies MD001.
- Around line 6-12: Remove the manual changes to the root CHANGELOG.md,
including the 8.4.1 section, and regenerate the changelog through the
repository’s release tooling so CI/CD remains the source of truth.

In `@cli/CHANGELOG.md`:
- Around line 13-15: Update the generated changelog heading hierarchy around the
8.4.0 release heading so its “Bug Fixes” subsection uses the next sequential
heading level instead of skipping level 2. Preserve consistent heading levels
for equivalent release sections throughout cli/CHANGELOG.md.

In `@cli/src/declarations.ts`:
- Around line 809-821: Add `@since` documentation tags to the public
PackageOptions interface and its symlink and moduleAliases members, using the
version that introduced each API and matching the existing documentation style
in declarations.ts.

In `@cli/src/ios/update.ts`:
- Around line 62-85: Move the awaited getCapacitorPackageVersion call out of the
validSPMPackages.map callback and compute iosPlatformVersion once before
Promise.all, then reuse it for majorCapVersion in each plugin iteration while
preserving the existing package update behavior.
- Around line 62-85: Update the per-plugin callback in the Promise.all map
around readFile and writeFile so each plugin’s Package.swift update is handled
independently. Catch failures within the callback, log a warning identifying the
affected plugin and error, and allow the remaining validSPMPackages updates to
continue.

In `@cli/src/util/spm.ts`:
- Around line 136-142: Update the symlink destination construction in the
plugin-processing flow to key the folder by the unique plugin.id rather than
plugin.name. Use the same ID-based folder consistently for symlinkFolder,
relPath, and the ensureSymlink destination, while preserving the existing
symlink behavior.

In `@core/src/core-plugins.ts`:
- Around line 265-269: Thread a generic type parameter through WebPlugin.request
so its Promise<HttpResponse> return type preserves the requested response data
type instead of defaulting to any. Update the method signature and
implementation at WebPlugin.request, including any required request/callback
typing, while preserving existing behavior and allowing callers to infer or
specify the response payload type.

In `@ios/CHANGELOG.md`:
- Around line 10-12: Update the generated changelog heading hierarchy around the
8.4.0 release entry so the “Features” heading does not skip level 2 beneath the
top-level release heading. Apply the consistent heading structure used by the
changelog generator for all release sections.
🪄 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: a4ea00d0-a852-4fb7-b31f-f0bb91019e24

📥 Commits

Reviewing files that changed from the base of the PR and between 3c62e7b and ebad857.

📒 Files selected for processing (26)
  • .github/workflows/test.yml
  • CHANGELOG.md
  • android/CHANGELOG.md
  • android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
  • android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
  • android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
  • android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java
  • android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java
  • android/package.json
  • cli/CHANGELOG.md
  • cli/package.json
  • cli/src/declarations.ts
  • cli/src/ios/update.ts
  • cli/src/ipc.ts
  • cli/src/tasks/run.ts
  • cli/src/telemetry.ts
  • cli/src/util/spm.ts
  • cli/test/telemetry.spec.ts
  • core/CHANGELOG.md
  • core/http.md
  • core/package.json
  • core/src/core-plugins.ts
  • ios/CHANGELOG.md
  • ios/Capacitor/Capacitor/PluginConfig.swift
  • ios/package.json
  • lerna.json
🔗 Linked repositories identified

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

  • Cap-go/capacitor-updater (manual)

runs-on: ubuntu-latest
# Keep this job capped at 10 minutes; never raise it unless explicitly asked.
timeout-minutes: 10
timeout-minutes: 30

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 | 🟠 Major | ⚡ Quick win

Preserve the 10-minute timeout contract for release-triggered tests.

.github/workflows/bump_version.yml explicitly states that called workflow jobs are capped at 10 minutes and must not be raised. These six changes allow those jobs to run for 30 minutes. Revert them to 10 minutes, or update the caller and documented policy with explicit approval.

Also applies to: 47-47, 63-63, 84-84, 105-105, 133-133

🤖 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 @.github/workflows/test.yml at line 33, Restore the timeout-minutes values in
all six affected jobs of the test workflow to 10, preserving the
release-triggered workflow contract documented by the caller. Update each
occurrence at the referenced job definitions and do not leave any at 30 minutes.

""";

private boolean insetHandlingEnabled = true;
private String insetsHandling = INSETS_HANDLING_CSS;

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 | 🟡 Minor | ⚡ Quick win

Case-sensitive comparison for insetsHandling inconsistent with style handling.

style is normalized with .toUpperCase(Locale.US) before comparison (line 96), but configuredInsetsHandling is compared as-is against INSETS_HANDLING_CSS/INSETS_HANDLING_DISABLE. A user setting "CSS" or "Disable" will silently fall back to css mode with a warning log, which is likely unintended given the case-insensitive precedent set for style.

🔧 Proposed fix
-        String configuredInsetsHandling = getConfig().getString("insetsHandling", INSETS_HANDLING_CSS);
+        String configuredInsetsHandling = getConfig().getString("insetsHandling", INSETS_HANDLING_CSS).toLowerCase(Locale.US);

Also applies to: 99-108

🤖 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` at
line 58, Normalize configuredInsetsHandling case-insensitively before comparing
it with INSETS_HANDLING_CSS and INSETS_HANDLING_DISABLE, matching the existing
style handling in SystemBars. Update the logic around the insetsHandling
configuration and its comparisons so values such as “CSS” and “Disable” select
their intended modes while preserving the existing fallback warning for
unsupported values.

Comment on lines +22 to +72
@Test
public void showWithEmptyBarShowsSystemBars() throws Exception {
WindowInsetsControllerCompat controller = invokeSetHidden("");

verify(controller).show(WindowInsetsCompat.Type.systemBars());
verify(controller, never()).show(WindowInsetsCompat.Type.statusBars());
verify(controller, never()).show(WindowInsetsCompat.Type.navigationBars());
}

@Test
public void showWithStatusBarShowsOnlyStatusBars() throws Exception {
WindowInsetsControllerCompat controller = invokeSetHidden("StatusBar");

verify(controller).show(WindowInsetsCompat.Type.statusBars());
verify(controller, never()).show(WindowInsetsCompat.Type.systemBars());
verify(controller, never()).show(WindowInsetsCompat.Type.navigationBars());
}

@Test
public void showWithNavigationBarShowsOnlyNavigationBars() throws Exception {
WindowInsetsControllerCompat controller = invokeSetHidden("NavigationBar");

verify(controller).show(WindowInsetsCompat.Type.navigationBars());
verify(controller, never()).show(WindowInsetsCompat.Type.systemBars());
verify(controller, never()).show(WindowInsetsCompat.Type.statusBars());
}

private WindowInsetsControllerCompat invokeSetHidden(String bar) throws Exception {
SystemBars plugin = new SystemBars();
Bridge bridge = mock(Bridge.class);
AppCompatActivity activity = mock(AppCompatActivity.class);
Window window = mock(Window.class);
View decorView = mock(View.class);
WindowInsetsControllerCompat controller = mock(WindowInsetsControllerCompat.class);

when(bridge.getActivity()).thenReturn(activity);
when(activity.getWindow()).thenReturn(window);
when(window.getDecorView()).thenReturn(decorView);

plugin.setBridge(bridge);

try (MockedStatic<WindowCompat> windowCompat = mockStatic(WindowCompat.class)) {
windowCompat.when(() -> WindowCompat.getInsetsController(window, decorView)).thenReturn(controller);

Method setHidden = SystemBars.class.getDeclaredMethod("setHidden", boolean.class, String.class);
setHidden.setAccessible(true);
setHidden.invoke(plugin, false, bar);
}

return controller;
}

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 | ⚡ Quick win

Test coverage gap: only hide=false (show) is exercised; hide path and insetsHandling validation are untested.

All three tests invoke setHidden with hide=false only, leaving the hide=true branch (lines 307-315 in SystemBars.java) unverified. Additionally, the new config-validation/fallback logic in initSystemBars() (unknown value → warning + fallback to "css") has no test coverage in this new test class.

Consider parameterizing invokeSetHidden to also test hide=true, and adding a test exercising initSystemBars() with valid (css, disable) and invalid insetsHandling config values.

🤖 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/test/java/com/getcapacitor/plugin/SystemBarsTest.java`
around lines 22 - 72, Extend SystemBarsTest beyond the current show-only
coverage by parameterizing invokeSetHidden to accept the hide flag and adding
assertions for hide=true that verify controller.hide calls for system, status,
and navigation bars. Add initSystemBars tests covering valid css and disable
insetsHandling values, plus an unknown value that verifies warning logging and
fallback to css.

Comment thread android/CHANGELOG.md
Comment on lines +10 to 12
# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)

### Bug Fixes

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 | 🟡 Minor | ⚡ Quick win

Fix the heading hierarchy.

The # 8.4.0 heading followed by ### Bug Fixes skips level 2 and triggers MD001. Use consistent heading levels in the generated changelog.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 12-12: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 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/CHANGELOG.md` around lines 10 - 12, Update the generated changelog
heading hierarchy around the 8.4.0 release heading so its “Bug Fixes” subsection
uses the immediate child heading level instead of skipping level 2. Preserve the
existing release heading and apply the same consistent hierarchy to generated
changelog sections.

Source: Linters/SAST tools

Comment thread CHANGELOG.md
Comment on lines +6 to +12
## [8.4.1](https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1) (2026-06-19)

### Bug Fixes

- **cli:** make SPM dependency patch work on prereleases ([#8508](https://github.com/ionic-team/capacitor/issues/8508)) ([6048e90](https://github.com/ionic-team/capacitor/commit/6048e90171afa0229a3c25b52a23c377c6bb804c))
- **cli:** patch Capacitor SPM dependency version in plugins ([#8492](https://github.com/ionic-team/capacitor/issues/8492)) ([28bb2c6](https://github.com/ionic-team/capacitor/commit/28bb2c687069dfdd6aa7abc866004a1c6388d103))

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 | 🟡 Minor | ⚡ Quick win

Do not manually edit the root changelog.

CHANGELOG.md is managed by CI/CD per the repository instructions. Regenerate this section through the release tooling rather than resolving it manually in this PR.

🤖 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 - 12, Remove the manual changes to the root
CHANGELOG.md, including the 8.4.1 section, and regenerate the changelog through
the repository’s release tooling so CI/CD remains the source of truth.

Source: Coding guidelines

Comment thread cli/src/declarations.ts
Comment on lines +809 to +821

export interface PackageOptions {
/**
* Create a symlink to the plugin folder instead of pointing to the plugin path.
* Useful when plugin names conflict.
*/
symlink?: boolean;
/**
* Useful to avoid target name conflicts in dependencies
* [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
*/
moduleAliases?: { [target: string]: string };
}

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 | ⚡ Quick win

Add @since tags to the new PackageOptions interface for consistency.

Every other public config field/interface in this file documents a @since version (e.g., packageOptions itself at Line 566 has @since 8.4.0), but the new PackageOptions interface and its symlink/moduleAliases members have none. This is likely rendered on the public docs site, so missing tags create an inconsistent/incomplete API reference.

📝 Proposed fix
 export interface PackageOptions {
   /**
    * Create a symlink to the plugin folder instead of pointing to the plugin path.
    * Useful when plugin names conflict.
+   *
+   * `@since` 8.4.0
    */
   symlink?: boolean;
   /**
    * Useful to avoid target name conflicts in dependencies
    * [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
+   *
+   * `@since` 8.4.0
    */
   moduleAliases?: { [target: string]: string };
 }
📝 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 interface PackageOptions {
/**
* Create a symlink to the plugin folder instead of pointing to the plugin path.
* Useful when plugin names conflict.
*/
symlink?: boolean;
/**
* Useful to avoid target name conflicts in dependencies
* [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
*/
moduleAliases?: { [target: string]: string };
}
export interface PackageOptions {
/**
* Create a symlink to the plugin folder instead of pointing to the plugin path.
* Useful when plugin names conflict.
*
* `@since` 8.4.0
*/
symlink?: boolean;
/**
* Useful to avoid target name conflicts in dependencies
* [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
*
* `@since` 8.4.0
*/
moduleAliases?: { [target: string]: string };
}
🤖 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/declarations.ts` around lines 809 - 821, Add `@since` documentation
tags to the public PackageOptions interface and its symlink and moduleAliases
members, using the version that introduced each API and matching the existing
documentation style in declarations.ts.

Comment thread cli/src/ios/update.ts
Comment on lines +62 to +85
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/majorCapVersion don't depend on plugin, but getCapacitorPackageVersion(config, config.ios.name) is awaited once per entry in validSPMPackages. Compute it once before Promise.all.

♻️ Proposed fix
     const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
+    const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
+    const majorCapVersion = major(iosPlatformVersion);
     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) {
📝 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);
const majorCapVersion = major(iosPlatformVersion);
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];
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 62 - 85, Move the awaited
getCapacitorPackageVersion call out of the validSPMPackages.map callback and
compute iosPlatformVersion once before Promise.all, then reuse it for
majorCapVersion in each plugin iteration while preserving the existing package
update behavior.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A5 'rootPath' cli/src/util/plugins.ts
rg -n -B2 -A15 'function checkPluginsForPackageSwift' cli/src

Repository: Cap-go/capacitor-plus

Length of output: 1390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== update.ts =="
sed -n '1,180p' cli/src/ios/update.ts

echo
echo "== spm.ts =="
sed -n '1,220p' cli/src/util/spm.ts

echo
echo "== rootPath search =="
rg -n "rootPath" cli/src

echo
echo "== Package.swift mutation search =="
rg -n "Package.swift|writeFile\\(packageSwiftPath|readFile\\(packageSwiftPath" cli/src

Repository: Cap-go/capacitor-plus

Length of output: 19913


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== update.ts (focused) =="
nl -ba cli/src/ios/update.ts | sed -n '1,170p'

echo
echo "== spm.ts =="
nl -ba cli/src/util/spm.ts | sed -n '1,220p'

echo
echo "== rootPath + Package.swift references =="
rg -n "rootPath|Package\\.swift|writeFile\\(packageSwiftPath|readFile\\(packageSwiftPath" cli/src

Repository: Cap-go/capacitor-plus

Length of output: 225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== plugin.ts (rootPath resolution) =="
sed -n '1,140p' cli/src/plugin.ts

echo
echo "== spm.ts (Package.swift existence check) =="
sed -n '280,320p' cli/src/util/spm.ts

echo
echo "== updateIOS / updatePluginFiles callers =="
rg -n "updateIOS\\(|updatePluginFiles\\(" cli/src

Repository: Cap-go/capacitor-plus

Length of output: 5078


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' cli/src/util/node.ts

Repository: Cap-go/capacitor-plus

Length of output: 1920


Handle each plugin Package.swift update independently. A single readFile/writeFile failure inside Promise.all(...map(...)) rejects the whole SPM update, so one bad plugin blocks every other plugin. Catch per plugin, log a warning, and continue.

🤖 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 62 - 85, Update the per-plugin callback
in the Promise.all map around readFile and writeFile so each plugin’s
Package.swift update is handled independently. Catch failures within the
callback, log a warning identifying the affected plugin and error, and allow the
remaining validSPMPackages updates to continue.

Comment thread cli/src/util/spm.ts
Comment on lines +136 to +142
const options = packageOptions[plugin.id];
const symlink = options?.symlink;
const symlinkFolder = join('symlinks', plugin.name);
const relPath = symlink ? symlinkFolder : relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
if (symlink) {
await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
}

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A10 'ensureSymlink' node_modules/fs-extra/lib/ensure/symlink.js 2>/dev/null || echo "fs-extra not vendored in sandbox"

Repository: Cap-go/capacitor-plus

Length of output: 192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== spm.ts around the reported lines ==\n'
sed -n '1,240p' cli/src/util/spm.ts | cat -n

printf '\n== search for PackageOptions and symlink docs/usages ==\n'
rg -n -A4 -B4 'PackageOptions|symlink' cli/src . -g '!**/node_modules/**' | sed -n '1,220p'

printf '\n== locate other symlink destination patterns ==\n'
rg -n "join\\('symlinks'|symlinks/" cli/src . -g '!**/node_modules/**' | sed -n '1,220p'

Repository: Cap-go/capacitor-plus

Length of output: 20857


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Plugin type definition ==\n'
sed -n '1,220p' cli/src/plugin.ts | cat -n

printf '\n== usages of plugin.id and plugin.name around path generation ==\n'
rg -n -A2 -B2 'plugin\.id|plugin\.name' cli/src/util/spm.ts cli/src/plugin.ts cli/src/declarations.ts | sed -n '1,220p'

Repository: Cap-go/capacitor-plus

Length of output: 10123


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('cli/src/util/spm.ts')
text = p.read_text()
for needle in ["symlinkFolder = join('symlinks', plugin.name)", "packageOptions[plugin.id]", "join('symlinks', plugin.id)"]:
    print(needle, 'FOUND' if needle in text else 'MISSING')
PY

Repository: Cap-go/capacitor-plus

Length of output: 279


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

node <<'JS'
const fs = require('fs/promises');
const path = require('path');
const os = require('os');

(async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'symlink-collision-'));
  const src1 = path.join(dir, 'plugin-a');
  const src2 = path.join(dir, 'plugin-b');
  const link = path.join(dir, 'symlinks', 'same-name');
  await fs.mkdir(src1, { recursive: true });
  await fs.mkdir(src2, { recursive: true });
  await fs.mkdir(path.dirname(link), { recursive: true });

  await fs.symlink(src1, link, 'dir');
  try {
    await fs.symlink(src2, link, 'dir');
    console.log('second symlink: unexpectedly succeeded');
  } catch (e) {
    console.log(`second symlink error: ${e.code}`);
  }

  const target = await fs.readlink(link);
  console.log(`final link target: ${target}`);
})();
JS

Length of output: 212


Key symlink destinations by plugin ID

PackageOptions.symlink is meant to handle plugin-name conflicts, but symlinks/<plugin.name> still collides for plugins with the same name. That makes the symlinked SPM package path non-unique and can break installs for one of the plugins.

🐛 Proposed fix
       const options = packageOptions[plugin.id];
       const symlink = options?.symlink;
-      const symlinkFolder = join('symlinks', plugin.name);
+      const symlinkFolder = join('symlinks', plugin.id);
       const relPath = symlink ? symlinkFolder : relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
📝 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
const options = packageOptions[plugin.id];
const symlink = options?.symlink;
const symlinkFolder = join('symlinks', plugin.name);
const relPath = symlink ? symlinkFolder : relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
if (symlink) {
await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
}
const options = packageOptions[plugin.id];
const symlink = options?.symlink;
const symlinkFolder = join('symlinks', plugin.id);
const relPath = symlink ? symlinkFolder : relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
if (symlink) {
await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
}
🤖 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/spm.ts` around lines 136 - 142, Update the symlink destination
construction in the plugin-processing flow to key the folder by the unique
plugin.id rather than plugin.name. Use the same ID-based folder consistently for
symlinkFolder, relPath, and the ensureSymlink destination, while preserving the
existing symlink behavior.

Comment thread core/src/core-plugins.ts
Comment on lines +265 to +269
export interface HttpResponse<T = any> {
/**
* Additional data received with the Http response.
*/
data: any;
data: T;

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 | ⚡ Quick win

Consider threading the generic through WebPlugin.request.

HttpResponse is converted to a generic interface HttpResponse<T = any>, and the data property changes from the untyped any to T, but the request implementation at line 404 still declares Promise<HttpResponse> (implicitly HttpResponse<any>), so callers get no compile-time benefit from the new generic unless they manually cast the result, as done in the updated docs (core/http.md).

♻️ Optional generic passthrough
-  async request(options: HttpOptions): Promise<HttpResponse> {
+  async request<T = any>(options: HttpOptions): Promise<HttpResponse<T>> {

Also applies to: 404-448

🤖 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 `@core/src/core-plugins.ts` around lines 265 - 269, Thread a generic type
parameter through WebPlugin.request so its Promise<HttpResponse> return type
preserves the requested response data type instead of defaulting to any. Update
the method signature and implementation at WebPlugin.request, including any
required request/callback typing, while preserving existing behavior and
allowing callers to infer or specify the response payload type.

Comment thread ios/CHANGELOG.md
Comment on lines +10 to +12
# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)

### Features

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 | 🟡 Minor | ⚡ Quick win

Fix the heading hierarchy.

The # 8.4.0 heading followed by ### Features skips level 2 and triggers MD001. Use consistent heading levels in the generated changelog.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 12-12: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 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/CHANGELOG.md` around lines 10 - 12, Update the generated changelog
heading hierarchy around the 8.4.0 release entry so the “Features” heading does
not skip level 2 beneath the top-level release heading. Apply the consistent
heading structure used by the changelog generator for all release sections.

Source: Linters/SAST tools

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.

8 participants