Skip to content

feat(rn): Capgo React Native updater + CLI with file-level delta#2703

Open
riderx wants to merge 5 commits into
mainfrom
feat/react-native-updater-cli
Open

feat(rn): Capgo React Native updater + CLI with file-level delta#2703
riderx wants to merge 5 commits into
mainfrom
feat/react-native-updater-cli

Conversation

@riderx

@riderx riderx commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Add @capgo/react-native-updater: React Native native module (iOS/Android) that speaks Capgo /updates + /stats and applies Capgo file-level delta manifests (SHA-256 + optional Brotli), same backend contract as @capgo/capacitor-updater.
  • Add @capgo/rn-cli (capgo-rn): Metro export of index.android.bundle + main.jsbundle + assets, then upload via @capgo/cli bundle upload --delta.
  • Extend @capgo/cli so upload/delta/brotli detection accepts @capgo/react-native-updater, and skip Capacitor index.html / notifyAppReady checks for RN export folders.

Motivation (AI generated)

React Native teams need Capgo-style OTA with the same Capgo Cloud delta system (not binary bspatch). This mirrors the RN integration pattern from zepto-labs/react-native-delta (JS bundle path override + check/download/apply), while reusing Capgo’s existing file-manifest delta pipeline end to end.

Business Impact (AI generated)

Opens Capgo live updates to React Native without forking storage/backend. One cloud, one delta format, new client + CLI surface.

Test Plan (AI generated)

  • bun run --cwd packages/rn-cli test
  • bun run --cwd packages/react-native-updater test
  • bun test ./cli/test/test-rn-updater-version.test.mjs
  • Wire a sample RN app: getJSBundleFile / getJSBundleURL, call notifyAppReady(), upload with capgo-rn upload <appId> --channel production, verify /updates returns manifest and only changed files download
  • Confirm Capacitor uploads still detect @capgo/capacitor-updater and require index.html / notifyAppReady unless --no-code-check

Generated with AI

Made with Cursor

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added React Native live-update support for Android and iOS, including bundle downloads, activation, rollback, channels, progress events, and bundle status management.
    • Added a React Native CLI with bundle, upload, and initialization commands.
    • Added file-level delta updates with SHA-256 checksums and optional Brotli compression.
  • Bug Fixes
    • Improved updater detection for both Capacitor and React Native projects.
    • Added support for React Native Metro export layouts during validation.
  • Documentation
    • Added setup, integration, usage, and CLI guidance for React Native updates.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds a React Native updater package with Android and iOS native implementations, a React Native bundling/upload CLI, updater-package detection in the existing CLI, related tests and documentation, and new Supabase user username typings.

Changes

React Native updater package

Layer / File(s) Summary
Package contracts and native wiring
packages/react-native-updater/src/*, packages/react-native-updater/package.json, packages/react-native-updater/ios/CapgoUpdater.m, packages/react-native-updater/react-native.config.js
Adds typed updater APIs, JavaScript native-module forwarding, package metadata, React Native registration, build configuration, documentation, and contract tests.
Android state and request services
packages/react-native-updater/android/src/main/java/.../BundleStore.kt, CapgoConfig.kt, CapgoHttp.kt
Adds bundle persistence, manifest configuration lookup, device metadata, HTTP requests, and statistics submission.
Android download and bridge flow
packages/react-native-updater/android/src/main/java/.../CapgoDownloader.kt, CapgoUpdater.kt, CapgoUpdaterModule.kt
Adds manifest/ZIP downloading, Brotli decompression, SHA-256 validation, cache reuse, bundle lifecycle operations, and React Native bridge methods.
iOS updater and archive handling
packages/react-native-updater/ios/CapgoUpdater.swift, CapgoBrotli.swift, CapgoZip.swift
Adds persisted bundle selection, update requests, downloads, lifecycle operations, Brotli support, ZIP extraction, checksum validation, and path safety checks.

CLI updater integration

Layer / File(s) Summary
Updater package detection and bundle behavior
cli/src/utils.ts, cli/src/bundle/partial.ts, cli/src/bundle/upload.ts, cli/test/test-rn-updater-version.test.mjs
Detects Capacitor or React Native updater packages and applies package kind and version to Brotli, validation, and checksum decisions.
CLI test commands
package.json
Adds scripts for running the React Native CLI and updater package tests.

React Native CLI

Layer / File(s) Summary
CLI commands and bundling
packages/rn-cli/src/index.ts, bundle.ts, package.json, test/*
Adds bundle, upload, and init commands plus Metro export handling and output-layout validation.
Upload and project setup
packages/rn-cli/src/upload.ts, init.ts, README.md
Adds Capgo upload execution, delta options, package checks, and setup instructions.

Supabase user typings

Layer / File(s) Summary
User identity type fields
src/types/supabase.types.ts, supabase/functions/_backend/utils/supabase.types.ts
Adds nullable Discord and GitHub username fields to generated user row, insert, and update types.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App as React Native app
  participant Bridge as CapgoUpdater bridge
  participant Server as Capgo update API
  participant Storage as Bundle storage
  App->>Bridge: getLatest(options)
  Bridge->>Server: POST update metadata
  Server-->>Bridge: version, URL, checksum, manifest
  App->>Bridge: download(options)
  Bridge->>Server: GET bundle files
  Bridge->>Storage: validate and persist bundle
  Storage-->>Bridge: bundle record
  Bridge-->>App: download result and events
  App->>Bridge: set({ id })
  Bridge->>Storage: select current bundle
Loading

Suggested reviewers: dalanir, wcaleniewolny

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning Summary and test plan are present, but the required Screenshots and Checklist sections are missing. Add the template sections for Screenshots and Checklist, and include checklist items plus concrete reproduction steps in Test plan.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding the React Native updater/CLI and file-level delta support.
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.

@socket-security

socket-security Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedreact-native-builder-bob@​0.30.3981007587100
Added@​types/​react@​18.3.311001007992100
Added@​types/​node@​22.20.11001008195100
Addedreact@​18.2.01001008497100
Added@​clack/​prompts@​0.11.01001009997100
Addedreact-native@​0.73.698100100100100

View full report

@socket-security

socket-security Bot commented Jul 15, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm commander is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/react-native@0.73.6npm/commander@9.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/commander@9.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm fast-xml-parser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/react-native@0.73.6npm/fast-xml-parser@4.5.7

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fast-xml-parser@4.5.7. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm yargs is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/react-native@0.73.6npm/react-native-builder-bob@0.30.3npm/yargs@17.7.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/yargs@17.7.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing feat/react-native-updater-cli (33939ef) with main (6e178ee)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (caa8138) during the generation of this report, so 6e178ee was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

riderx and others added 2 commits July 16, 2026 01:47
Introduce @capgo/react-native-updater and @capgo/rn-cli so RN apps can use
the same Capgo /updates + manifest delta path as Capacitor, plus CLI upload
wiring that detects the RN updater package.

Co-authored-by: Cursor <cursoragent@cursor.com>
Knip flagged CapgoUpdater + default as duplicate exports.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx riderx force-pushed the feat/react-native-updater-cli branch from 3384aae to 8c4465f Compare July 15, 2026 23:47
Auto-sync on main regenerated types without the columns from the social
usernames migration, breaking backend typecheck against updated tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx riderx marked this pull request as ready for review July 15, 2026 23:59
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8c8360e0-fd25-481c-9e2c-3ea5610f5dc1)

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 15, 2026

Copy link
Copy Markdown

We've triggered an ultrareview automatically — This PR introduces a new React Native native updater with file-level delta manifests, Brotli decompression, and state management across 42 files, modifying core CLI upload logic and package resolution.... I'll post findings when complete.

An ultrareview is cubic's deepest review, catching hard-to-find bugs in the most critical PRs. It runs a longer, multi-pass analysis using cubic's most capable review models, and typically takes around 30 minutes. It consumes your team's reviewed-lines allowance at 3× the standard rate.

Automated ultrareviews are disabled by default. We triggered this run as part of your trial. Want cubic to do this for every high-risk PR? Enable auto-ultrareview in your settings.

@cursor cursor Bot requested review from Dalanir and WcaleNieWolny July 16, 2026 00:00

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

Stale comment

Risk: high. Cursor Bugbot did not complete (usage limit reached), so automated review is incomplete. Human review is required for this new React Native updater and CLI changes; reviewers assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

Pin Android deps with verification metadata, simplify download APIs,
handle file op return values, and clean unused Swift/TS parameters.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 16, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_66f14708-5dc4-45e1-ac77-164d0302f6fb)

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

Stale comment

Risk: high. Cursor Bugbot did not complete (usage limit reached), so automated review is incomplete. Human review remains required; Dalanir and WcaleNieWolny are already assigned.

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.

Stale comment

Risk: high. Cursor Bugbot did not complete (usage limit reached), so automated review is incomplete. Human review is still required; two reviewers are already assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

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

Stale comment

Risk: high. Not approving: Cursor Bugbot skipped (usage limit reached), so the required automated review did not complete. This PR adds a new native React Native updater and CLI upload changes that need human review.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

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

All reported issues were addressed across 8 files (changes from recent commits).

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread packages/react-native-updater/android/gradle/verification-metadata.xml Outdated
Reject encrypted Capgo session keys until crypto is ported, canonicalize
manifest/zip paths under the bundle dir, and drop the ineffective
verification-metadata file.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 16, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_0ba567b7-28ae-41c1-bc19-952cc8722523)

@riderx

riderx commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Review follow-up (AI generated)

Addressed cubic P1/P2 findings in 33939efdb:

  • Canonicalize manifest + zip extraction paths under the bundle directory (path traversal)
  • Explicitly reject encrypted Capgo updates (sessionKey) until crypto is ported
  • Soft temp-file cleanup (no finally masking)
  • Removed ineffective packaged verification-metadata.xml; versions remain pinned in build.gradle

@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: high. Not approving: Cursor Bugbot skipped (usage limit reached), so the required automated review did not complete. Human review is still required for this new native React Native updater and CLI changes; two reviewers are already assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@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: high. Not approving: Cursor Bugbot skipped (usage limit reached), so the required automated review did not complete. Human review is still required for this native React Native updater and CLI changes; Dalanir and WcaleNieWolny are already assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 28

🤖 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 `@packages/react-native-updater/android/src/main/AndroidManifest.xml`:
- Around line 1-3: Add the android.permission.INTERNET uses-permission
declaration to the AndroidManifest manifest so the updater can perform HTTP
update checks and downloads when the host application does not declare network
access.

In
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/BundleStore.kt`:
- Around line 55-85: Update BundleStore’s list, save, and upsert methods to
synchronize index mutations so concurrent list→modify→save operations cannot
overwrite each other. Make save write the JSON to a temporary file in the same
directory, then atomically replace bundles.json, preserving the existing index
contents on write failures and preventing readers from seeing partial JSON.

In
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoDownloader.kt`:
- Around line 56-59: Update the download flow around request.url and downloadZip
to pass request.checksum through, then compute the downloaded ZIP’s SHA-256 and
compare it with that checksum before any extraction or activation. Abort the
update on mismatch, preserving the existing success stats only for verified
archives, and update all downloadZip call sites including the additional
referenced path.
- Around line 131-139: Refactor the download flow around findCachedByHash so the
existing cached bundles are recursively scanned and hashed once per download,
producing a hash-to-file index. Pass and reuse this index for each manifest
entry instead of rescanning the cache on every call, while preserving the
existing reuse and writeDownloadedFile behavior.

In
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoHttp.kt`:
- Around line 42-55: Update postJson to check response.isSuccessful before
parsing the body as a JSONObject update payload. For non-2xx responses, return
or surface an error result containing the HTTP status and response details so
getLatest cannot interpret the failure as updateAvailable; preserve the existing
JSON parsing and invalid_json fallback for successful responses.

In
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterModule.kt`:
- Around line 177-181: Reorder the operations in CapgoUpdaterModule: resolve the
promise with bundleToMap(record) before invoking CapgoUpdater.reload for the
selected-bundle flow at
packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterModule.kt
lines 177-181, and apply the same resolve-before-reload ordering for the
builtin-bundle flow at lines 208-210.
- Around line 134-164: Enforce successful terminal status before activation: in
packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterModule.kt:134-164,
update the download failure path to persist the record as error and remove
partial files; at :172-180, require record.status == "success" in set; at
:193-197, require successful status and complete files in next; and in
packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdater.kt:27-36,
allow promotion only for successful records.
- Around line 182-184: Read and store the nullable option ID before entering the
try/catch surrounding the set operation, using the existing options access
pattern and guarding missing or null values as needed. Update the catch block to
reuse that captured ID in CapgoHttp.sendStats instead of calling
options.getString("id") again, ensuring the failure path can always reach
promise.reject.

In `@packages/react-native-updater/ios/CapgoBrotli.swift`:
- Around line 13-32: Update decodeBrotli to avoid relying on the fixed 6×/64 KiB
dstCapacity guess. Use compression_stream or a retry loop that enlarges the
output buffer when compression_decode_buffer cannot fit the decoded payload,
while preserving successful Data output and the existing failure error.

In `@packages/react-native-updater/ios/CapgoUpdater.swift`:
- Around line 364-389: Update set and next to validate the requested bundle ID
using the index and confirm its bundle file exists before writing
capgo_current_bundle_id or capgo_next_bundle_id. Reject unknown or incomplete
IDs and return without persisting preferences or sending stats; preserve the
existing success flow for valid bundles.
- Around line 255-274: Update the ZIP download flow in the updater method
containing the manifest and url branches to compute the downloaded archive’s
SHA-256 checksum and compare it with the supplied checksum before extraction or
marking the bundle successful. Reject the download on mismatch, while preserving
the existing behavior for valid ZIP downloads and the manifest branch.
- Around line 375-377: Remove the exit(0) termination from the set and reset
activation paths in CapgoUpdater.swift. Replace it with React Native bridge
reload behavior, or defer activation until the next normal app launch, while
preserving the existing update/reset flow and avoiding host-process termination.
- Around line 40-49: Update applyPendingNext() to check capgo_app_ready before
promoting the pending bundle. Persist the current bundle ID as the previous
bundle when applying an update, and if the prior launch was not marked ready,
restore that previous bundle instead of promoting the pending one; ensure the
relevant UserDefaults keys are updated consistently.
- Around line 193-196: Replace the whole-file loading in sha256(path:) and the
related download/archive/install flows with file-backed URLSessionDownloadTask
handling and incremental SHA-256 updates. Stream response data directly to disk,
then hash files in chunks rather than loading them into Data, preserving the
existing checksum and processing behavior while avoiding simultaneous full-size
allocations.
- Around line 161-183: Refactor postJson to avoid semaphore-based synchronous
waiting and expose an asynchronous completion or async/throws result to its
callers. Configure explicit request and resource timeouts on the
URLSession/request, and update every caller of postJson to await or handle the
asynchronous result while preserving JSON parsing and error propagation.
- Around line 218-233: Update the response maps in the updater callback around
sendEvent and resolve to avoid storing Swift optionals as Any: conditionally add
optional error fields so missing values are omitted, and validate required
version, URL, and session_key fields before constructing or resolving the
success map. Preserve the existing noNeedUpdate event and resolution behavior
while ensuring all emitted values are React Native-compatible.
- Around line 118-121: Update saveIndex(_:) to write the serialized bundle index
atomically, using a temporary file and replacing or renaming the existing index
only after the write succeeds. Stop suppressing serialization and file-write
errors; propagate failures through the saveIndex(_:) API and its callers so
unsuccessful updates are observable.

In `@packages/react-native-updater/ios/CapgoZip.swift`:
- Around line 24-33: Update the ZIP entry parsing flow around nameData and
payload in CapgoZip.swift to validate every computed range before calling
Data.subdata(in:), including the filename range and payload range derived from
nameLen, extraLen, and compSize. Throw the existing extraction NSError with a
clear invalid or truncated entry message when any range exceeds data.count,
while preserving normal parsing for valid entries.
- Around line 82-101: Update the decompression logic around the stream
processing in CapgoZip to decode in bounded output chunks, enforce a maximum
extracted-size limit independent of the ZIP header’s expectedSize, and continue
processing until the stream reaches COMPRESSION_STATUS_END. Treat
COMPRESSION_STATUS_OK as incomplete rather than success, rejecting streams that
exceed the cap or cannot reach END, and only return the fully decoded output
after successful completion.

In `@packages/react-native-updater/README.md`:
- Around line 34-40: Update the AppDelegate release bundle URL guidance to fall
back to the packaged bundle URL when CapgoUpdater.getJSBundleURL() returns nil,
preserving the Capgo URL when available. Reference CapgoUpdater.getJSBundleURL()
and the existing packaged-bundle URL implementation, and ensure first and reset
installations always return a valid release bundle.

In `@packages/react-native-updater/src/definitions.ts`:
- Around line 1-4: Keep ManifestEntry nullable for server responses, and
introduce a separate request manifest type with required file_name and
download_url while omitting file_hash. Update DownloadOptions.manifest and the
download flow to use this request-only type, ensuring LatestVersion.manifest is
validated or transformed before being passed to download rather than forwarding
nullable fields to the Android bridge.

In `@packages/react-native-updater/src/index.ts`:
- Around line 68-73: Update addListener in the unlinked-emitter branch to throw
the existing LINKING_ERROR instead of returning a no-op remove subscription.
Preserve the current emitter.addListener registration and removal behavior when
the native module is linked.

In `@packages/rn-cli/README.md`:
- Around line 27-32: Add the text language identifier to the fenced
export-layout example in the README, changing the opening fence before
.capgo-rn/export/ while leaving the example contents unchanged.

In `@packages/rn-cli/src/bundle.ts`:
- Around line 66-95: Wrap the export loop and its bundle-output validation in a
try/catch so failures from run or the missing-output check stop the spinner
before propagating. In the catch associated with the spinner started by the
export flow, stop it with a failure message, then rethrow the original error;
preserve the existing success stop and completion logging.
- Around line 15-21: Update the run function’s spawn options to avoid the
Windows shell fallback: keep shell disabled and invoke a direct Windows-safe
executable path so --entry-file and other args remain literal argv values.
Preserve the existing exit and error handling behavior.

In `@packages/rn-cli/src/upload.ts`:
- Around line 60-70: Validate the delta options in the upload flow before
invoking Capgo: reject configurations where `options.delta === false` and
`options.deltaOnly` is enabled. Keep the existing argument construction for
valid combinations, ensuring `--delta-only` is never forwarded without
`--delta`.
- Around line 23-30: Update the run function’s spawn invocation to avoid shell
parsing on Windows by removing the platform-based shell option or otherwise
using a Windows-safe direct executable/.cmd launcher. Preserve the existing cwd,
stdio, environment, and exit/error handling behavior.

In `@packages/rn-cli/test/export-layout.test.ts`:
- Around line 7-18: Update the test expected Capgo delta folder shape to
exercise production behavior through runBundle rather than creating the asserted
files directly. Mock the Metro executable and invoke runBundle, then retain
assertions for index.android.bundle, main.jsbundle, and assets/img.png;
alternatively, test the production layout validator if that is the exposed
implementation path.
🪄 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: df7fe8be-4676-4afc-8d1e-e6db7da07d62

📥 Commits

Reviewing files that changed from the base of the PR and between caa8138 and 33939ef.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • cli/src/bundle/partial.ts
  • cli/src/bundle/upload.ts
  • cli/src/utils.ts
  • cli/test/test-rn-updater-version.test.mjs
  • package.json
  • packages/react-native-updater/.gitignore
  • packages/react-native-updater/CapgoReactNativeUpdater.podspec
  • packages/react-native-updater/README.md
  • packages/react-native-updater/android/build.gradle
  • packages/react-native-updater/android/gradle.properties
  • packages/react-native-updater/android/src/main/AndroidManifest.xml
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/BundleStore.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoConfig.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoDownloader.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoHttp.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdater.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterModule.kt
  • packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterPackage.kt
  • packages/react-native-updater/ios/CapgoBrotli.swift
  • packages/react-native-updater/ios/CapgoUpdater.m
  • packages/react-native-updater/ios/CapgoUpdater.swift
  • packages/react-native-updater/ios/CapgoZip.swift
  • packages/react-native-updater/package.json
  • packages/react-native-updater/react-native.config.js
  • packages/react-native-updater/src/__tests__/api.test.ts
  • packages/react-native-updater/src/definitions.ts
  • packages/react-native-updater/src/index.ts
  • packages/react-native-updater/src/version.ts
  • packages/react-native-updater/tsconfig.build.json
  • packages/react-native-updater/tsconfig.json
  • packages/rn-cli/.gitignore
  • packages/rn-cli/README.md
  • packages/rn-cli/package.json
  • packages/rn-cli/src/bundle.ts
  • packages/rn-cli/src/index.ts
  • packages/rn-cli/src/init.ts
  • packages/rn-cli/src/upload.ts
  • packages/rn-cli/test/export-layout.test.ts
  • packages/rn-cli/tsconfig.json
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
🔗 Linked repositories identified

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

  • Cap-go/capacitor-updater (manual)

Comment on lines +1 to +3
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.capgo.rnupdater">
</manifest>

Copy link
Copy Markdown
Contributor

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

Declare the network permission required by the updater.

The module performs HTTP downloads, but its merged manifest contributes no android.permission.INTERNET; host apps that do not already declare it cannot check or download updates.

Proposed fix
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="app.capgo.rnupdater">
+  <uses-permission android:name="android.permission.INTERNET" />
 </manifest>
📝 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
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.capgo.rnupdater">
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.capgo.rnupdater">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
🤖 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 `@packages/react-native-updater/android/src/main/AndroidManifest.xml` around
lines 1 - 3, Add the android.permission.INTERNET uses-permission declaration to
the AndroidManifest manifest so the updater can perform HTTP update checks and
downloads when the host application does not declare network access.

Comment on lines +55 to +85
fun list(context: Context): List<BundleRecord> {
val file = File(root(context), INDEX)
if (!file.exists()) return emptyList()
return try {
val arr = JSONArray(file.readText())
(0 until arr.length()).map { i ->
val o = arr.getJSONObject(i)
BundleRecord(
id = o.getString("id"),
version = o.optString("version", ""),
status = o.optString("status", "success"),
checksum = o.optString("checksum", ""),
downloaded = o.optString("downloaded", ""),
)
}
} catch (_: Exception) {
emptyList()
}
}

fun save(context: Context, bundles: List<BundleRecord>) {
val arr = JSONArray()
bundles.forEach { arr.put(it.toJson()) }
File(root(context), INDEX).writeText(arr.toString())
}

fun upsert(context: Context, record: BundleRecord) {
val all = list(context).filter { it.id != record.id }.toMutableList()
all.add(record)
save(context, all)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make bundle-index updates synchronized and atomic.

Concurrent downloads execute list → modify → save on a cached thread pool, so one upsert can overwrite another. Directly writing bundles.json also lets readers observe truncated JSON; Line 70 then silently converts that corruption into an empty index, enabling subsequent permanent data loss.

Serialize index mutations and write a temporary file followed by an atomic replacement.

🤖 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
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/BundleStore.kt`
around lines 55 - 85, Update BundleStore’s list, save, and upsert methods to
synchronize index mutations so concurrent list→modify→save operations cannot
overwrite each other. Make save write the JSON to a temporary file in the same
directory, then atomically replace bundles.json, preserving the existing index
contents on write failures and preventing readers from seeing partial JSON.

Comment on lines +56 to +59
request.url.isNotEmpty() && !request.url.contains("404.capgo.app") -> {
CapgoHttp.sendStats(context, "download_zip_start", request.version)
downloadZip(canonicalDest, request.url, progress)
CapgoHttp.sendStats(context, "download_zip_complete", request.version)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Verify the full ZIP against request.checksum before extraction.

The ZIP path never consumes the supplied checksum, yet the downloaded JavaScript is subsequently activated. A corrupted or tampered archive can therefore be installed despite the advertised SHA-256 contract.

Pass the checksum into downloadZip, compare it with sha256(zipFile), and abort before extraction on mismatch.

Also applies to: 201-217

🤖 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
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoDownloader.kt`
around lines 56 - 59, Update the download flow around request.url and
downloadZip to pass request.checksum through, then compute the downloaded ZIP’s
SHA-256 and compare it with that checksum before any extraction or activation.
Abort the update on mismatch, preserving the existing success stats only for
verified archives, and update all downloadZip call sites including the
additional referenced path.

Comment on lines +131 to +139
val reused = findCachedByHash(context, fileHash, dest)
if (reused != null) {
reused.copyTo(target, overwrite = true)
} else {
writeDownloadedFile(context, version, fileName, downloadUrl, target, isBrotli)
}

verifyChecksum(context, version, fileName, fileHash, target)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Build the cache hash index once per download.

For every manifest entry, findCachedByHash recursively rereads and hashes every file in every existing bundle. A manifest and cached bundle containing (N) files require roughly (N²) full-file reads, making large delta updates unnecessarily slow and battery-intensive.

Also applies to: 183-199

🤖 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
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoDownloader.kt`
around lines 131 - 139, Refactor the download flow around findCachedByHash so
the existing cached bundles are recursively scanned and hashed once per
download, producing a hash-to-file index. Pass and reuse this index for each
manifest entry instead of rescanning the cache on every call, while preserving
the existing reuse and writeDownloadedFile behavior.

Comment on lines +42 to +55
fun postJson(url: String, body: JSONObject): JSONObject {
val request = Request.Builder()
.url(url)
.post(body.toString().toRequestBody("application/json".toMediaType()))
.header("User-Agent", "CapgoRNUpdater/${CapgoConfig.PLUGIN_VERSION}")
.build()
client.newCall(request).execute().use { response ->
val text = response.body?.string() ?: "{}"
return try {
JSONObject(text)
} catch (_: Exception) {
JSONObject().put("error", "invalid_json").put("message", text.take(200))
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify every postJson caller and whether HTTP status is handled elsewhere.
rg -n -C4 '\bpostJson\s*\(|\bisSuccessful\b' packages/react-native-updater/android

Repository: Cap-go/capgo.app

Length of output: 5044


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- CapgoHttp.kt ---'
sed -n '1,220p' packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoHttp.kt

echo
echo '--- CapgoUpdaterModule.kt ---'
sed -n '1,180p' packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoUpdaterModule.kt

Repository: Cap-go/capgo.app

Length of output: 9488


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- updateUrl / response contract references ---'
rg -n -C3 'updateUrl|session_key|noNeedUpdate|updateAvailable|invalid_json|manifest' packages/react-native-updater ../../Cap-go/capacitor-updater 2>/dev/null || true

echo
echo '--- README / docs mentions ---'
rg -n -C2 'session_key|noNeedUpdate|updateAvailable|updateUrl|manifest' . 2>/dev/null | head -n 200

Repository: Cap-go/capgo.app

Length of output: 50372


Handle non-2xx responses before treating the body as an update payload. postJson always parses the response body, so a 4xx/5xx JSON response without an error field can still flow through getLatest as updateAvailable with empty or invalid fields. Check response.isSuccessful and surface the HTTP failure instead.

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 42-43: An outbound HTTP request is built from a non-literal URL: URL(...).openConnection(), OkHttp's Request.Builder().url(...), or a Spring RestTemplate call (getForObject/getForEntity/postForObject/postForEntity/exchange/execute). When the URL is derived from a variable, method call, concatenation, or interpolation, an attacker can point the request at internal services, cloud metadata endpoints (e.g. 169.254.169.254), or arbitrary hosts (Server-Side Request Forgery). Do not build request targets from untrusted input: validate the URL against a strict allowlist of permitted hosts/schemes, resolve and re-check the host after DNS resolution, and reject internal/link-local/loopback addresses before connecting.
Context: Request.Builder()
.url(url)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-outbound-request-tainted-url-kotlin)

🤖 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
`@packages/react-native-updater/android/src/main/java/app/capgo/rnupdater/CapgoHttp.kt`
around lines 42 - 55, Update postJson to check response.isSuccessful before
parsing the body as a JSONObject update payload. For non-2xx responses, return
or surface an error result containing the HTTP status and response details so
getLatest cannot interpret the failure as updateAvailable; preserve the existing
JSON parsing and invalid_json fallback for successful responses.

Comment on lines +15 to +21
function run(cmd: string, args: string[], cwd: string): Promise<void> {
return new Promise((resolvePromise, reject) => {
const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' })
child.on('error', reject)
child.on('exit', (code) => {
if (code === 0) resolvePromise()
else reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## packages/rn-cli/src/bundle.ts\n'
cat -n packages/rn-cli/src/bundle.ts | sed -n '1,220p'

printf '\n## usages of run(\n'
rg -n "spawn\\(|shell: process\\.platform === 'win32'|new Spinner|spinner\\." packages/rn-cli/src -n

printf '\n## nearby package metadata\n'
cat -n packages/rn-cli/package.json | sed -n '1,220p'

Repository: Cap-go/capgo.app

Length of output: 5838


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## runBundle usages\n'
rg -n "runBundle\\(" packages/rn-cli/src

printf '\n## full bundle.ts with line numbers around the loop\n'
cat -n packages/rn-cli/src/bundle.ts | sed -n '50,120p'

printf '\n## upload.ts for the same spawn pattern\n'
cat -n packages/rn-cli/src/upload.ts | sed -n '1,120p'

Repository: Cap-go/capgo.app

Length of output: 5884


Avoid the Windows shell fallback here. shell: true on line 17 lets cmd.exe parse --entry-file and the other CLI values as shell syntax instead of literal argv items, so user-controlled input can become command injection. Keep shell: false and use a direct Windows-safe executable path instead.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 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 `@packages/rn-cli/src/bundle.ts` around lines 15 - 21, Update the run
function’s spawn options to avoid the Windows shell fallback: keep shell
disabled and invoke a direct Windows-safe executable path so --entry-file and
other args remain literal argv values. Preserve the existing exit and error
handling behavior.

Source: Linters/SAST tools

Comment on lines +66 to +95
const s = spinner()
s.start(`Exporting Metro bundles (${platforms.join(', ')})`)

const metroBin = findMetroBin(project)
const useNode = metroBin.endsWith('.js')

for (const platform of platforms) {
const bundleOut = platform === 'ios'
? join(out, 'main.jsbundle')
: join(out, 'index.android.bundle')
const assetsDest = join(out, 'assets')
const args = [
...(useNode ? [metroBin] : []),
'bundle',
'--platform', platform,
'--dev', options.dev ? 'true' : 'false',
'--entry-file', entryFile,
'--bundle-output', bundleOut,
'--assets-dest', assetsDest,
'--reset-cache',
]
const cmd = useNode ? process.execPath : metroBin
await run(cmd, args, project)
if (!existsSync(bundleOut)) {
throw new Error(`Metro did not produce ${bundleOut}`)
}
}

s.stop(color.green(`Export ready at ${out}`))
log.info(`Files: ${readdirSync(out).join(', ')}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Stop the spinner on export failures.

A rejected run() call or the missing-output error bypasses Line 94, leaving an active spinner before the top-level error is printed. Wrap the export loop in try/catch, stop it with a failure message, then rethrow.

Proposed fix
   const s = spinner()
   s.start(`Exporting Metro bundles (${platforms.join(', ')})`)
 
-  for (const platform of platforms) {
-    // export work
+  try {
+    for (const platform of platforms) {
+      // export work
+    }
+    s.stop(color.green(`Export ready at ${out}`))
+  }
+  catch (error) {
+    s.stop(color.red('Export failed'))
+    throw error
   }
-
-  s.stop(color.green(`Export ready at ${out}`))
📝 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 s = spinner()
s.start(`Exporting Metro bundles (${platforms.join(', ')})`)
const metroBin = findMetroBin(project)
const useNode = metroBin.endsWith('.js')
for (const platform of platforms) {
const bundleOut = platform === 'ios'
? join(out, 'main.jsbundle')
: join(out, 'index.android.bundle')
const assetsDest = join(out, 'assets')
const args = [
...(useNode ? [metroBin] : []),
'bundle',
'--platform', platform,
'--dev', options.dev ? 'true' : 'false',
'--entry-file', entryFile,
'--bundle-output', bundleOut,
'--assets-dest', assetsDest,
'--reset-cache',
]
const cmd = useNode ? process.execPath : metroBin
await run(cmd, args, project)
if (!existsSync(bundleOut)) {
throw new Error(`Metro did not produce ${bundleOut}`)
}
}
s.stop(color.green(`Export ready at ${out}`))
log.info(`Files: ${readdirSync(out).join(', ')}`)
const s = spinner()
s.start(`Exporting Metro bundles (${platforms.join(', ')})`)
const metroBin = findMetroBin(project)
const useNode = metroBin.endsWith('.js')
try {
for (const platform of platforms) {
const bundleOut = platform === 'ios'
? join(out, 'main.jsbundle')
: join(out, 'index.android.bundle')
const assetsDest = join(out, 'assets')
const args = [
...(useNode ? [metroBin] : []),
'bundle',
'--platform', platform,
'--dev', options.dev ? 'true' : 'false',
'--entry-file', entryFile,
'--bundle-output', bundleOut,
'--assets-dest', assetsDest,
'--reset-cache',
]
const cmd = useNode ? process.execPath : metroBin
await run(cmd, args, project)
if (!existsSync(bundleOut)) {
throw new Error(`Metro did not produce ${bundleOut}`)
}
}
s.stop(color.green(`Export ready at ${out}`))
}
catch (error) {
s.stop(color.red('Export failed'))
throw error
}
log.info(`Files: ${readdirSync(out).join(', ')}`)
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 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 `@packages/rn-cli/src/bundle.ts` around lines 66 - 95, Wrap the export loop and
its bundle-output validation in a try/catch so failures from run or the
missing-output check stop the spinner before propagating. In the catch
associated with the spinner started by the export flow, stop it with a failure
message, then rethrow the original error; preserve the existing success stop and
completion logging.

Comment on lines +23 to +30
function run(cmd: string, args: string[], cwd: string): Promise<void> {
return new Promise((resolvePromise, reject) => {
const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32', env: process.env })
child.on('error', reject)
child.on('exit', (code) => {
if (code === 0) resolvePromise()
else reject(new Error(`${cmd} exited with ${code}`))
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline packages/rn-cli/src/upload.ts --view expanded
rg -n -C3 "shell:|runUpload\\s*\\(|capgoCli" packages/rn-cli

Repository: Cap-go/capgo.app

Length of output: 3412


Do not route this spawn through cmd.exe on Windows. shell: process.platform === 'win32' lets CLI-controlled paths and flags be parsed by the shell, so legitimate values with metacharacters can break or redirect the upload. Spawn the executable directly, or use a Windows-safe .cmd launcher without shell parsing.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 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 `@packages/rn-cli/src/upload.ts` around lines 23 - 30, Update the run
function’s spawn invocation to avoid shell parsing on Windows by removing the
platform-based shell option or otherwise using a Windows-safe direct
executable/.cmd launcher. Preserve the existing cwd, stdio, environment, and
exit/error handling behavior.

Source: Linters/SAST tools

Comment on lines +60 to +70
const useDelta = options.delta !== false
const args = [
'bundle', 'upload',
appId,
'--path', exportPath,
'--channel', options.channel,
'--no-code-check',
]

if (useDelta) args.push('--delta')
if (options.deltaOnly) args.push('--delta-only')

Copy link
Copy Markdown
Contributor

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

Reject conflicting --no-delta and --delta-only options.

The current logic can omit --delta while still forwarding --delta-only. Validate these options as mutually exclusive before invoking Capgo.

Proposed fix
   const useDelta = options.delta !== false
+  if (!useDelta && options.deltaOnly) {
+    throw new Error('--delta-only cannot be combined with --no-delta')
+  }
+
   const args = [
📝 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 useDelta = options.delta !== false
const args = [
'bundle', 'upload',
appId,
'--path', exportPath,
'--channel', options.channel,
'--no-code-check',
]
if (useDelta) args.push('--delta')
if (options.deltaOnly) args.push('--delta-only')
const useDelta = options.delta !== false
if (!useDelta && options.deltaOnly) {
throw new Error('--delta-only cannot be combined with --no-delta')
}
const args = [
'bundle', 'upload',
appId,
'--path', exportPath,
'--channel', options.channel,
'--no-code-check',
]
if (useDelta) args.push('--delta')
if (options.deltaOnly) args.push('--delta-only')
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 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 `@packages/rn-cli/src/upload.ts` around lines 60 - 70, Validate the delta
options in the upload flow before invoking Capgo: reject configurations where
`options.delta === false` and `options.deltaOnly` is enabled. Keep the existing
argument construction for valid combinations, ensuring `--delta-only` is never
forwarded without `--delta`.

Comment on lines +7 to +18
test('expected Capgo delta folder shape', () => {
const dir = join(tmpdir(), `capgo-rn-export-${Date.now()}`)
mkdirSync(join(dir, 'assets'), { recursive: true })
writeFileSync(join(dir, 'index.android.bundle'), 'android')
writeFileSync(join(dir, 'main.jsbundle'), 'ios')
writeFileSync(join(dir, 'assets', 'img.png'), 'x')

expect(existsSync(join(dir, 'index.android.bundle'))).toBe(true)
expect(existsSync(join(dir, 'main.jsbundle'))).toBe(true)
expect(existsSync(join(dir, 'assets', 'img.png'))).toBe(true)

rmSync(dir, { recursive: true, force: true })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Exercise the production export logic instead of recreating its expected output.

This test writes every asserted file itself, so changes that break runBundle cannot fail the test. Invoke runBundle with a mocked Metro executable, or extract and test the production layout validator.

🤖 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 `@packages/rn-cli/test/export-layout.test.ts` around lines 7 - 18, Update the
test expected Capgo delta folder shape to exercise production behavior through
runBundle rather than creating the asserted files directly. Mock the Metro
executable and invoke runBundle, then retain assertions for
index.android.bundle, main.jsbundle, and assets/img.png; alternatively, test the
production layout validator if that is the exposed implementation path.

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/react-native-updater/android/build.gradle">

<violation number="1" location="packages/react-native-updater/android/build.gradle:26">
P3: This comment does not suppress the intended `text:S8569` finding: `NOSONAR` applies only to issues raised on the comment's own line, not subsequent Gradle declarations. Consider a scoped `sonar.issue.ignore.multicriteria` entry for this rule and file instead.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

}
}

// NOSONAR text:S8569 - library pins versions; consuming app owns Gradle dependency locking

@cubic-dev-ai cubic-dev-ai Bot Jul 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This comment does not suppress the intended text:S8569 finding: NOSONAR applies only to issues raised on the comment's own line, not subsequent Gradle declarations. Consider a scoped sonar.issue.ignore.multicriteria entry for this rule and file instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-native-updater/android/build.gradle, line 26:

<comment>This comment does not suppress the intended `text:S8569` finding: `NOSONAR` applies only to issues raised on the comment's own line, not subsequent Gradle declarations. Consider a scoped `sonar.issue.ignore.multicriteria` entry for this rule and file instead.</comment>

<file context>
@@ -23,6 +23,7 @@ android {
   }
 }
 
+// NOSONAR text:S8569 - library pins versions; consuming app owns Gradle dependency locking
 configurations.configureEach {
   resolutionStrategy {
</file context>
Fix with cubic

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.

1 participant