Skip to content

Conversation

@transphorm
Copy link
Member

@transphorm transphorm commented Oct 8, 2025

  • refine mobile deploy auto pr logic

  • remove env check

  • fix get-version

Summary by CodeRabbit

  • New Features

    • Automated creation of version-bump pull requests from staging to dev with dynamic titles and safe no-op behavior.
    • Exposed workflow outputs so version and build info are available to downstream steps.
  • Chores

    • Unified version bump flow driven by PR labels/inputs and a centralized bump job; release tagging now follows this flow.
    • Increased timeouts for Android and iOS e2e build jobs.
  • Refactor

    • CI-driven version/build verification and centralized version management replacing per-platform local bump steps.

* refine mobile deploy auto pr logic

* remove env check

* fix get-version
@transphorm transphorm changed the title refine mobile deploy auto pr logic (#1234) staging: test mobile deploy auto pull request (#1234) Oct 8, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Walkthrough

Adds a composite GitHub Action to create version-bump PRs, centralizes version bump detection and PR creation in the mobile-deploy workflow, introduces a Node-based version-manager script and updates Fastlane to read/verify CI-provided version/build numbers instead of performing local bumps.

Changes

Cohort / File(s) Summary
Composite GitHub action: create version bump PR
.github/actions/create-version-bump-pr/action.yml
New composite action that checks Git config, compares staging vs dev, exits early if no new commits, creates a branch from staging, pushes it, and opens a platform-aware PR via gh pr create using inputs platform, version, file_paths, github_token.
Workflow: mobile deploy / version flow
.github/workflows/mobile-deploy.yml
Centralizes version-bump logic: adds bump-version and create-version-bump-pr jobs, new inputs (dry_run, bump_target_branch), emits outputs (version, ios_build, android_build, version_bump_type, platform, pr_title), replaces per-platform bump PR steps, and updates release tagging to depend on the new flow.
Action: expose version output
.github/actions/get-version/action.yml
Adds a version output wired to the get-version step (sets GITHUB_OUTPUT and GITHUB_ENV).
E2E workflow timeouts
.github/workflows/mobile-e2e.yml
Increases job timeouts: android-build-test and e2e-ios from 60 → 120 minutes.
Fastlane: CI-driven version handling
app/fastlane/Fastfile
Removes in-flow semantic bumps; Fastlane lanes now read version and build numbers from version.json and rely on CI/central tooling. Passes version_bump through without performing npm bumps; updates ios build output dir and Android version handling to use CI-provided values.
Fastlane helper: version verification
app/fastlane/helpers/version_manager.rb
Removes separate per-platform bump methods; adds verify_ci_version_match which validates CI env variables (CI_VERSION, CI_IOS_BUILD, CI_ANDROID_BUILD) against local package/version data and returns consolidated { version, ios_build, android_build } or raises on mismatch.
New version manager script (Node)
app/scripts/version-manager.cjs
New CLI module exposing read/write helpers and operations: readPackageJson, readVersionJson, getVersionInfo, bumpVersion(bumpType, platform), applyVersions(version, iosBuild, androidBuild). Supports get, bump, and apply commands with validation and explicit error handling.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor CI as GitHub Actions
  participant BUMP as bump-version job
  participant VM as version-manager.cjs
  participant PR_ACTION as create-version-bump-pr (composite action)
  participant GH as GitHub / gh CLI

  CI->>BUMP: determine bump type (labels or input)
  BUMP->>VM: getVersionInfo() / bump/apply if requested
  BUMP-->>CI: outputs (version, ios_build, android_build, platform, version_bump_type)
  CI->>PR_ACTION: inputs(platform, version, file_paths, github_token)
  PR_ACTION->>PR_ACTION: git config, fetch staging/dev, compare commits
  alt no new commits
    PR_ACTION-->>CI: exit early (no PR)
  else commits ahead
    PR_ACTION->>GH: create branch from staging, push, gh pr create (base=bump_target_branch)
    GH-->>CI: PR URL / result
  end
Loading
sequenceDiagram
  autonumber
  participant Fastlane as Fastlane lanes
  participant Helper as version_manager.rb
  participant VM as version-manager.cjs
  Fastlane->>Helper: verify_ci_version_match() (reads CI env)
  alt CI values valid
    Helper-->>Fastlane: {version, ios_build, android_build}
    Fastlane->>Fastlane: use values for increment_build_number / increment_version_code (no local semantic bump)
  else mismatch
    Helper-->>Fastlane: raise CI version mismatch error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • remicolin
  • aaronmgdr

Poem

Branches hum, a tiny bot at play,
It reads the version, nudges not the day.
Cherry-pick wings and PRs set loose,
CI guards the numbers, quiet as a truce.
One script, one bump — a tidy, gentle coup. ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title includes environment and test context along with a reference to another PR number without clearly conveying the actual updates to the mobile deploy automation or version retrieval logic, making it overly verbose and not focused on the main change. Please update the title to a concise summary of the main changes, for example “Refine mobile deploy auto PR logic and fix get-version step,” removing the staging/test prefix and PR number reference.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin/test-mobile-deploy-auto-pr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@transphorm transphorm marked this pull request as ready for review October 8, 2025 04:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
app/fastlane/Fastfile (2)

104-106: Add error handling for the version bump script.

The Node.js one-liner lacks error handling. If the semver package is missing from dependencies or if the script fails for any reason, the failure won't be gracefully handled.

Consider adding validation or error handling:

 when "major", "minor", "patch"
   # Use Node.js with semver to bump version
-  sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
+  begin
+    sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
+  rescue => e
+    UI.user_error!("Failed to bump version: #{e.message}. Ensure 'semver' is in package.json dependencies.")
+  end
   UI.success("✅ Bumped #{version_bump} version")

307-309: Add error handling for the version bump script.

The Node.js one-liner lacks error handling. If the semver package is missing from dependencies or if the script fails for any reason, the failure won't be gracefully handled.

Consider adding validation or error handling:

 when "major", "minor", "patch"
   # Use Node.js with semver to bump version
-  sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
+  begin
+    sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
+  rescue => e
+    UI.user_error!("Failed to bump version: #{e.message}. Ensure 'semver' is in package.json dependencies.")
+  end
   UI.success("✅ Bumped #{version_bump} version")
.github/actions/create-version-bump-pr/action.yml (1)

36-50: Add error handling for git operations.

Multiple git operations (checkout, fetch, cherry-pick, push) can fail but lack error handling. For example, the branch might already exist, cherry-pick could encounter conflicts, or the push could fail if the branch exists remotely.

Consider adding error handling and cleanup:

       # Commit changes on temporary branch
       git checkout -b temp-version-commit
       git commit -m "chore: bump ${{ inputs.platform }} version for ${{ inputs.version }} [skip ci]"
       
       # Create new branch from dev
       git fetch origin dev
-      git checkout -b ${BRANCH_NAME} origin/dev
+      if git show-ref --verify --quiet refs/heads/${BRANCH_NAME}; then
+        echo "Branch ${BRANCH_NAME} already exists locally, deleting..."
+        git branch -D ${BRANCH_NAME}
+      fi
+      git checkout -b ${BRANCH_NAME} origin/dev
       
       # Cherry-pick only the version changes
-      git cherry-pick temp-version-commit
+      if ! git cherry-pick temp-version-commit; then
+        echo "❌ Cherry-pick failed. Aborting..."
+        git cherry-pick --abort
+        git checkout -
+        git branch -D temp-version-commit
+        exit 1
+      fi
       
       # Clean up temporary branch
       git branch -D temp-version-commit
       
       # Push and create PR
-      git push --set-upstream origin ${BRANCH_NAME}
+      git push --force --set-upstream origin ${BRANCH_NAME}
.github/workflows/mobile-deploy.yml (1)

155-173: Consider using jq for more robust label parsing.

Using grep -q on JSON output can be fragile if label names contain special characters or if the JSON structure changes. Consider using jq for more robust parsing:

         # Override with PR label if present
         if [ "${{ github.event_name }}" = "pull_request" ]; then
           LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
-          if echo "$LABELS" | grep -q "version:major"; then
+          if echo "$LABELS" | jq -e '.[] | select(. == "version:major")' > /dev/null; then
             VERSION_BUMP="major"
-          elif echo "$LABELS" | grep -q "version:minor"; then
+          elif echo "$LABELS" | jq -e '.[] | select(. == "version:minor")' > /dev/null; then
             VERSION_BUMP="minor"
-          elif echo "$LABELS" | grep -q "version:patch"; then
+          elif echo "$LABELS" | jq -e '.[] | select(. == "version:patch")' > /dev/null; then
             VERSION_BUMP="patch"
           fi
         fi

This is more precise and handles edge cases better, though the current implementation should work for simple label names.

Also applies to: 746-764

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5b0ba5 and 3b4c937.

📒 Files selected for processing (3)
  • .github/actions/create-version-bump-pr/action.yml (1 hunks)
  • .github/workflows/mobile-deploy.yml (5 hunks)
  • app/fastlane/Fastfile (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.{yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/**/*.{yml,yaml}: In GitHub workflows, use the shared composite actions in .github/actions for dependency caching instead of calling actions/cache directly
Use the cache-yarn composite action for Yarn dependency caching in workflows
Use the cache-bundler composite action for Ruby gems caching in workflows
Use the cache-gradle composite action for Gradle caching in workflows
Use the cache-pods composite action for CocoaPods caching in workflows

Files:

  • .github/workflows/mobile-deploy.yml
🪛 actionlint (1.7.7)
.github/workflows/mobile-deploy.yml

1058-1058: property "version" is not defined in object type {}

(expression)

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: analyze-ios
  • GitHub Check: build-deps
  • GitHub Check: analyze-android
  • GitHub Check: e2e-ios
  • GitHub Check: android-build-test
🔇 Additional comments (1)
.github/workflows/mobile-deploy.yml (1)

1069-1074: Clarify release tagging dependency on PR creation.

The create-release-tags job depends on create-version-bump-pr, but the version bump PR needs to be merged before creating release tags. This dependency ensures the PR is created successfully but doesn't ensure it's merged.

Is the intent to:

  1. Create tags immediately after the PR is created (current behavior), or
  2. Create tags only after the version bump PR is merged?

If (2), this job should be triggered by a separate workflow that runs when the version bump PR is merged to dev. The current dependency chain doesn't ensure the PR is merged before creating tags.

@transphorm transphorm marked this pull request as draft October 9, 2025 16:23
…1244)

* bump version to match staging

* save wip

* deploy fixes

* fix version setting

* update version logic

* fix version pr

* increase timeout to 2 hours

* pr logic tweaks

* fix script

* fix script path

* add comments and update logic to test from feature branch

* fix build path

* fix version input error

* fix pulling version

* add skip-deploy lable

* address cr concners
@transphorm transphorm marked this pull request as ready for review October 10, 2025 07:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (4)
.github/workflows/mobile-e2e.yml (3)

140-144: Avoid listing private repo contents in CI logs.

ls -la app/android/android-passport-nfc-reader | head -10 can leak private repo structure in public logs. Prefer a presence check or a specific file assertion without directory listing.

Apply this change:

-            echo "📁 android-passport-nfc-reader contents:"
-            ls -la app/android/android-passport-nfc-reader/ | head -10
+            # Verify expected files without listing the directory
+            test -f app/android/android-passport-nfc-reader/README.md || echo "ℹ️ README not present (ok)"

118-123: Add Gradle wrapper validation to mitigate supply‑chain risks.

Validate the Gradle wrapper before executing to prevent tampering.

Insert this step before building:

       - name: Build Android APK
+      - name: Validate Gradle Wrapper
+        uses: gradle/wrapper-validation-action@v2
+        with:
+          min-wrapper-count: 1
+          allow-snapshots: false
+      - name: Build Android APK
         run: |
           echo "Building Android APK..."
           chmod +x app/android/gradlew
           (cd app/android && ./gradlew assembleDebug --quiet --parallel --build-cache --no-configuration-cache) || { echo "❌ Android build failed"; exit 1; }
           echo "✅ Android build succeeded"

241-243: Pin Maestro installer to a fixed version
Unpinned curl | bash is a supply-chain risk. Define a stable MAESTRO_VERSION (e.g. in job-level env) and use it in the install step per official docs; optionally verify checksum.

 .github/workflows/mobile-e2e.yml
@@ -240,7 +240,11 @@ jobs:
             # …
-            - name: Install Maestro
-              if: steps.cache-maestro.outputs.cache-hit != 'true'
-              run: curl -Ls "https://get.maestro.mobile.dev" | bash
+            - name: Install Maestro (pinned)
+              if: steps.cache-maestro.outputs.cache-hit != 'true'
+              env:
+                MAESTRO_VERSION: ${{ env.MAESTRO_VERSION }}
+              run: |
+                curl -Ls "https://get.maestro.mobile.dev" | bash
+                maestro --version

+ at top of workflow:
+ env:
+   MAESTRO_VERSION: 1.2.3  # pin to a specific release
.github/workflows/mobile-deploy.yml (1)

381-381: Scope disabling Yarn hardened mode to only the failing command
Global export of YARN_ENABLE_HARDENED_MODE=0 weakens supply‐chain protections. Identify which step fails under hardened mode and apply the override only to that command (e.g. YARN_ENABLE_HARDENED_MODE=0 yarn install) or fix the underlying issue to preserve hardened mode.

🧹 Nitpick comments (2)
app/fastlane/helpers/version_manager.rb (1)

30-35: Add error handling for package.json reads

get_current_version raises low-signal exceptions if package.json is missing/invalid. Mirror read_version_file’s guard/handling for reliability.

Outside the shown lines, consider:

def get_current_version
  package_json_path = File.expand_path("../../package.json", __dir__)
  unless File.exist?(package_json_path)
    UI.user_error!("package.json not found at #{package_json_path}")
  end

  JSON.parse(File.read(package_json_path))["version"]
rescue JSON::ParserError => e
  UI.user_error!("Failed to parse package.json: #{e.message}")
end
app/scripts/version-manager.cjs (1)

89-100: Validate version.json shape and numeric types before use

Guard against missing keys or non-numeric builds to fail fast with actionable errors. Prevents downstream CI/Fastlane mismatches.

Apply this diff:

 function getVersionInfo() {
   const pkg = readPackageJson();
   const versionData = readVersionJson();
 
+  if (!versionData || typeof versionData !== 'object') {
+    throw new Error('version.json: invalid structure');
+  }
+  const ios = versionData.ios;
+  const android = versionData.android;
+  if (!ios || typeof ios.build !== 'number') {
+    throw new Error('version.json: ios.build must be a number');
+  }
+  if (!android || typeof android.build !== 'number') {
+    throw new Error('version.json: android.build must be a number');
+  }
+
   return {
     version: pkg.version,
     iosBuild: versionData.ios.build,
     androidBuild: versionData.android.build,
     iosLastDeployed: versionData.ios.lastDeployed,
     androidLastDeployed: versionData.android.lastDeployed,
   };
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b4c937 and a66c590.

📒 Files selected for processing (7)
  • .github/actions/create-version-bump-pr/action.yml (1 hunks)
  • .github/actions/get-version/action.yml (1 hunks)
  • .github/workflows/mobile-deploy.yml (18 hunks)
  • .github/workflows/mobile-e2e.yml (2 hunks)
  • app/fastlane/Fastfile (6 hunks)
  • app/fastlane/helpers/version_manager.rb (1 hunks)
  • app/scripts/version-manager.cjs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.{yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/**/*.{yml,yaml}: In GitHub workflows, use the shared composite actions in .github/actions for dependency caching instead of calling actions/cache directly
Use the cache-yarn composite action for Yarn dependency caching in workflows
Use the cache-bundler composite action for Ruby gems caching in workflows
Use the cache-gradle composite action for Gradle caching in workflows
Use the cache-pods composite action for CocoaPods caching in workflows

Files:

  • .github/workflows/mobile-e2e.yml
  • .github/workflows/mobile-deploy.yml
🧠 Learnings (2)
📚 Learning: 2025-10-08T20:23:58.783Z
Learnt from: transphorm
PR: selfxyz/self#1244
File: .github/workflows/mobile-deploy.yml:774-776
Timestamp: 2025-10-08T20:23:58.783Z
Learning: In the selfxyz/self repository, for the mobile deployment workflow (.github/workflows/mobile-deploy.yml):
- iOS builds cache Ruby gems at `app/ios/vendor/bundle`
- Android builds cache Ruby gems at `app/vendor/bundle`
- These paths should be used consistently within their respective build jobs

Applied to files:

  • .github/workflows/mobile-deploy.yml
📚 Learning: 2025-10-04T05:29:43.587Z
Learnt from: CR
PR: selfxyz/self#0
File: AGENTS.md:0-0
Timestamp: 2025-10-04T05:29:43.587Z
Learning: Applies to .github/workflows/**/*.{yml,yaml} : Use the cache-bundler composite action for Ruby gems caching in workflows

Applied to files:

  • .github/workflows/mobile-deploy.yml
🪛 RuboCop (1.81.1)
app/fastlane/Fastfile

[warning] 136-136: Useless assignment to variable - version_bump.

(Lint/UselessAssignment)


[warning] 307-307: Useless assignment to variable - version_bump. Did you mean version_code?

(Lint/UselessAssignment)

app/fastlane/helpers/version_manager.rb

[convention] 47-78: Assignment Branch Condition size for verify_ci_version_match is too high. [<9, 20, 9> 23.71/23]

(Metrics/AbcSize)

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build-deps
  • GitHub Check: android-build-test
  • GitHub Check: e2e-ios
  • GitHub Check: analyze-android
  • GitHub Check: analyze-ios
🔇 Additional comments (2)
.github/workflows/mobile-e2e.yml (2)

37-37: Timeout increase to 120m: OK — monitor runner utilization.

Looks reasonable for heavier builds. Keep an eye on queue times and job cancellations under the concurrency group.


181-181: iOS timeout to 120m: OK — confirm org limits on macOS large runners.

macos-latest-large capacity/time quotas vary by plan; ensure this won’t throttle other pipelines.

Comment on lines +28 to +36
# Ensure we're on staging branch, not detached HEAD
git fetch origin staging dev
git checkout staging
# Check if staging has commits not in dev (version bumps + any build changes)
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
if [ "$COMMITS_AHEAD" -eq 0 ]; then
echo "ℹ️ No new commits on staging compared to dev. Skipping PR creation."
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make branch checkout and ahead-count robust against detached HEADs

git checkout staging can fail if no local branch exists. Also compute ahead using remote refs to avoid local divergence.

Apply this diff:

-        # Ensure we're on staging branch, not detached HEAD
-        git fetch origin staging dev
-        git checkout staging
+        # Ensure we're on staging branch (freshly tracking remote), not detached HEAD
+        set -euo pipefail
+        git fetch --prune origin
+        git checkout -B staging origin/staging
@@
-        COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
+        COMMITS_AHEAD=$(git rev-list --count origin/dev..origin/staging)

Also applies to: 40-47

🤖 Prompt for AI Agents
.github/actions/create-version-bump-pr/action.yml around lines 28-36 (and
similarly apply to lines 40-47): make the branch checkout robust by creating or
resetting the local staging branch from the remote instead of a plain checkout,
and compute the "commits ahead" count using remote refs only (compare origin/dev
to origin/staging) to avoid relying on local branch state or a detached HEAD;
update the script to fetch remotes, force-create/reset the local staging branch
to origin/staging, and replace the rev-list range with the
origin/dev..origin/staging remote-only range, and apply the same changes to the
other occurrence at lines 40-47.

Comment on lines +19 to 24
id: get-version
shell: bash
run: |
VERSION=$(node -p "require('${{ inputs.app_path }}/package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_ENV
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Node require path to avoid resolving a module instead of a file

require('${{ inputs.app_path }}/package.json') may resolve a package in node_modules if app_path doesn’t start with ./ or /. Use path.resolve and strict shell flags.

Apply this diff:

-    - name: Get version from package.json
-      id: get-version
-      shell: bash
-      run: |
-        VERSION=$(node -p "require('${{ inputs.app_path }}/package.json').version")
-        echo "version=$VERSION" >> $GITHUB_OUTPUT
-        echo "VERSION=$VERSION" >> $GITHUB_ENV
+    - name: Get version from package.json
+      id: get-version
+      shell: bash
+      run: |
+        set -euo pipefail
+        VERSION=$(node -p "require(require('path').resolve('${{ inputs.app_path }}', 'package.json')).version")
+        echo "version=$VERSION" >> \"$GITHUB_OUTPUT\"
+        echo "VERSION=$VERSION" >> \"$GITHUB_ENV\"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/actions/get-version/action.yml lines 19-24: the current shell command
uses node -p "require('${{ inputs.app_path }}/package.json').version" which can
cause Node to resolve a package from node_modules when app_path is not a file
path and lacks strict shell error handling; change the script to enable strict
shell flags (set -euo pipefail) and use Node to require a resolved absolute path
(e.g., path.resolve(process.cwd(), inputPath, 'package.json')) or otherwise call
require with a fully resolved path so Node loads the intended file, and
propagate any errors so the action fails fast.

Comment on lines +156 to +161
outputs:
version: ${{ steps.bump.outputs.version }}
ios_build: ${{ steps.bump.outputs.ios_build }}
android_build: ${{ steps.bump.outputs.android_build }}
version_bump_type: ${{ steps.determine-bump.outputs.version_bump }}
platform: ${{ steps.determine-platform.outputs.platform }}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

bump-version step outputs are not emitted (breaks downstream).

Job outputs map to steps.bump.outputs.*, but the bump run step doesn’t write to $GITHUB_OUTPUT. Downstream uses of needs.bump-version.outputs.version/builds will be empty, causing apply/build steps and PR/tagging to misbehave.

Fix by emitting outputs after the bump calculation (parse app/version.json as source of truth):

       - name: Bump version using version-manager script
         id: bump
         run: |
           cd ${{ env.APP_PATH }}

           VERSION_BUMP="${{ steps.determine-bump.outputs.version_bump }}"
           PLATFORM="${{ steps.determine-platform.outputs.platform }}"

           echo "🔄 Calculating version bump..."
           echo "  Type: $VERSION_BUMP"
           echo "  Platform: $PLATFORM"
           echo ""

           # Use version-manager script to calculate bump
           # NOTE: Using absolute path to ensure script is found regardless of CWD
           node ${{ env.APP_PATH }}/scripts/version-manager.cjs bump "$VERSION_BUMP" "$PLATFORM"

           echo ""
           echo "✅ Version bump calculated successfully"
           echo "⚠️  Note: Changes are local only. Will be committed in PR after successful builds."
+
+          # Export bump results for downstream jobs
+          VERSION=$(node -e "console.log(require('./version.json').version)")
+          IOS_BUILD=$(node -e "console.log(require('./version.json').ios?.build ?? '')")
+          ANDROID_BUILD=$(node -e "console.log(require('./version.json').android?.build ?? '')")
+
+          if [ -z "$VERSION" ] || [ -z "$IOS_BUILD" ] || [ -z "$ANDROID_BUILD" ]; then
+            echo "❌ Failed to read version/builds from version.json"
+            exit 1
+          fi
+          {
+            echo "version=$VERSION"
+            echo "ios_build=$IOS_BUILD"
+            echo "android_build=$ANDROID_BUILD"
+          } >> "$GITHUB_OUTPUT"

To confirm the Node script already sets outputs (if that’s the intended design), run:

Also applies to: 223-243


🏁 Script executed:

#!/bin/bash
# Verify version-manager.cjs writes to $GITHUB_OUTPUT
fd -a version-manager.cjs
rg -n "GITHUB_OUTPUT|set-output" -S --type js --type ts

Length of output: 50


Emit bump-version step outputs for downstream jobs

Downstream mappings needs.bump-version.outputs.* are empty because the bump step never writes to $GITHUB_OUTPUT, breaking version tags, builds and PR automation. After running node …/version-manager.cjs bump …, add:

+          # Export bump results for downstream jobs
+          VERSION=$(node -e "console.log(require('./version.json').version)")
+          IOS_BUILD=$(node -e "console.log(require('./version.json').ios?.build ?? '')")
+          ANDROID_BUILD=$(node -e "console.log(require('./version.json').android?.build ?? '')")
+          if [ -z "$VERSION" ] || [ -z "$IOS_BUILD" ] || [ -z "$ANDROID_BUILD" ]; then
+            echo "❌ Failed to read version/builds from version.json" && exit 1
+          fi
+          {
+            echo "version=$VERSION"
+            echo "ios_build=$IOS_BUILD"
+            echo "android_build=$ANDROID_BUILD"
+          } >> "$GITHUB_OUTPUT"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/mobile-deploy.yml around lines 156-161: the bump step invoked
via node does not write any values to $GITHUB_OUTPUT, so downstream mappings
(needs.bump-version.outputs.*) are empty; after running the node
version-manager.cjs bump command capture its JSON output (or parse stdout) and
append the required keys to $GITHUB_OUTPUT (e.g., version, ios_build,
android_build, version_bump, platform) using the recommended echo "key=value" >>
$GITHUB_OUTPUT pattern so the step with id "bump" exposes those outputs for
downstream jobs.

Comment on lines +791 to 794
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
echo "Ruby gems: $GEMS_SIZE"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Ruby gems path in cache usage reports (iOS vs Android).

The iOS job reports from app/vendor/bundle and Android from app/ios/vendor/bundle, which is inverted. Use platform-correct paths to avoid misleading diagnostics.

Based on learnings

-          if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
-            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
+          if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
+            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
             echo "Ruby gems: $GEMS_SIZE"
           fi
-          if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
-            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
+          if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
+            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
             echo "Ruby gems: $GEMS_SIZE"
           fi

Also applies to: 1207-1210

🤖 Prompt for AI Agents
.github/workflows/mobile-deploy.yml around lines 791-794: the script currently
checks "${{ env.APP_PATH }}/vendor/bundle" for the iOS job but that path is
inverted (iOS should check "${{ env.APP_PATH }}/ios/vendor/bundle" and Android
should check "${{ env.APP_PATH }}/vendor/bundle"); update the path used in this
iOS block to "${{ env.APP_PATH }}/ios/vendor/bundle" and make the corresponding
inversion fix in the similar block at lines 1207-1210 so each platform reports
the correct vendor/bundle location.

Comment on lines +1453 to +1460
# Push all tags (force to handle any conflicts)
if git push origin --tags 2>/dev/null; then
echo "🚀 Tags pushed to repository"
else
echo "⚠️ Some tags may already exist on remote, trying force push..."
git push origin --tags --force
echo "🚀 Tags force-pushed to repository"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid force-pushing all tags; push only the tags created by this run.

git push origin --tags with a force fallback can overwrite existing tags and push unrelated tags. This is risky for release integrity.

Push only the intended tags and never force:

-          # Push all tags (force to handle any conflicts)
-          if git push origin --tags 2>/dev/null; then
-            echo "🚀 Tags pushed to repository"
-          else
-            echo "⚠️  Some tags may already exist on remote, trying force push..."
-            git push origin --tags --force
-            echo "🚀 Tags force-pushed to repository"
-          fi
+          # Push only tags created in this run; skip if they already exist remotely
+          push_tag_safe() {
+            local TAG="$1"
+            if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then
+              echo "⏭️  ${TAG} already exists on remote"
+            else
+              git push origin "${TAG}"
+              echo "🚀 Pushed ${TAG}"
+            fi
+          }
+
+          push_tag_safe "v${VERSION}"
+          if [ "${{ needs.build-ios.result }}" = "success" ]; then
+            push_tag_safe "v${VERSION}-ios-${IOS_BUILD}"
+          fi
+          if [ "${{ needs.build-android.result }}" = "success" ]; then
+            push_tag_safe "v${VERSION}-android-${ANDROID_BUILD}"
+          fi
📝 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
# Push all tags (force to handle any conflicts)
if git push origin --tags 2>/dev/null; then
echo "🚀 Tags pushed to repository"
else
echo "⚠️ Some tags may already exist on remote, trying force push..."
git push origin --tags --force
echo "🚀 Tags force-pushed to repository"
fi
# Push only tags created in this run; skip if they already exist remotely
push_tag_safe() {
local TAG="$1"
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then
echo "⏭️ ${TAG} already exists on remote"
else
git push origin "${TAG}"
echo "🚀 Pushed ${TAG}"
fi
}
push_tag_safe "v${VERSION}"
if [ "${{ needs.build-ios.result }}" = "success" ]; then
push_tag_safe "v${VERSION}-ios-${IOS_BUILD}"
fi
if [ "${{ needs.build-android.result }}" = "success" ]; then
push_tag_safe "v${VERSION}-android-${ANDROID_BUILD}"
fi
🤖 Prompt for AI Agents
.github/workflows/mobile-deploy.yml around lines 1453 to 1460: current logic
force-pushes all tags which can overwrite unrelated tags; instead capture the
specific tags created in this workflow run (e.g., collect tag names produced
earlier or compare repo state before/after to list newly created tags), then if
the list is non-empty push those tags explicitly with `git push origin <tag1>
<tag2> ...` and do not use --force; if no new tags were created, skip pushing
and log that no tags need to be pushed.

Comment on lines +98 to 106
# In CI, version should already be set by version-manager.cjs
# Verify it matches expected values
if is_ci && version_bump == "skip"
Fastlane::Helpers.verify_ci_version_match
end

# Prepare and build
result = prepare_ios_build(prod_release: deployment_track == "production")
# Prepare and build (no version bumping inside)
result = prepare_ios_build(prod_release: deployment_track == "production", version_bump: version_bump)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify helper namespace usage (Helpers vs Helpers::VersionManager)

Calls like Fastlane::Helpers.verify_ci_version_match and get_{ios,android}_build_number assume these are exposed on Helpers. In this PR, they are defined under Helpers::VersionManager. If wrappers aren’t present, lanes will crash at runtime.

Run this to confirm wrapper methods exist on Fastlane::Helpers:

If wrappers are missing, either update call sites to Fastlane::Helpers::VersionManager.* or add delegating methods on Helpers.

Also applies to: 175-178, 291-295, 332-335


🏁 Script executed:

#!/bin/bash
# Verify wrappers on Fastlane::Helpers
rg -nPU '(?s)^\s*module\s+Fastlane\b.*?^\s*module\s+Helpers\b.*?^\s*def\s+(verify_ci_version_match|get_ios_build_number|get_android_build_number|update_deployment_timestamp)\b' -g 'app/fastlane/**'

Length of output: 2447


Fix helper namespace usage
Calls to Fastlane::Helpers.verify_ci_version_match, get_ios_build_number, get_android_build_number, and update_deployment_timestamp reference methods defined under Fastlane::Helpers::VersionManager. Either add delegating methods in Fastlane::Helpers or update these call sites to Fastlane::Helpers::VersionManager.* (app/fastlane/Fastfile lines 98-106, 175-178, 291-295, 332-335)

🤖 Prompt for AI Agents
In app/fastlane/Fastfile around lines 98-106 (and also update call sites at
~175-178, ~291-295, ~332-335), calls currently refer to methods on
Fastlane::Helpers (e.g., Fastlane::Helpers.verify_ci_version_match,
get_ios_build_number, get_android_build_number, update_deployment_timestamp) but
the implementations live under Fastlane::Helpers::VersionManager; change those
call sites to call Fastlane::Helpers::VersionManager.method_name (or
alternatively add thin delegating methods on Fastlane::Helpers that forward to
Fastlane::Helpers::VersionManager) so the correct namespace is used
consistently.

export_method: "app-store",
output_directory: "build",
output_directory: "ios/build",
clean: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix iOS output_directory path (wrong CWD)

"ios/build" is relative and likely resolves under app/fastlane, not app/ios. Use a path anchored to this file to avoid build output in the wrong place.

Apply this diff:

-      output_directory: "ios/build",
+      output_directory: File.expand_path("../ios/build", __dir__),

Recommend normalizing other iOS/Android paths similarly (xcodeproj, Podfile, gradle_file_path, project_dir) to use File.expand_path with dir for deterministic paths.

🤖 Prompt for AI Agents
In app/fastlane/Fastfile around line 228 the output_directory is set to a
relative "ios/build" which will resolve against the Fastfile CWD (app/fastlane)
and write build artifacts to the wrong folder; change it to an absolute path
anchored to this Fastfile using File.expand_path(File.join(__dir__, '..', 'ios',
'build')) (or equivalent) so the path always points to app/ios/build, and apply
the same pattern (File.expand_path with __dir__) to other iOS/Android paths
(xcodeproj, Podfile, gradle_file_path, project_dir) to ensure deterministic,
correct locations.

Comment on lines +53 to +60
pkg_version = get_current_version
ios_build = get_ios_build_number
android_build = get_android_build_number

expected_version = ENV["CI_VERSION"]
expected_ios_build = ENV["CI_IOS_BUILD"].to_i
expected_android_build = ENV["CI_ANDROID_BUILD"].to_i

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden type handling to avoid false mismatches and ambiguous failures

Coerce build numbers to integers on both sides and use ENV.fetch for clearer errors. This prevents subtle mismatches if version.json contains strings and surfaces missing ENV early.

Apply this diff:

-        pkg_version = get_current_version
-        ios_build = get_ios_build_number
-        android_build = get_android_build_number
+        pkg_version = get_current_version
+        ios_build = Integer(get_ios_build_number)
+        android_build = Integer(get_android_build_number)
@@
-        expected_version = ENV["CI_VERSION"]
-        expected_ios_build = ENV["CI_IOS_BUILD"].to_i
-        expected_android_build = ENV["CI_ANDROID_BUILD"].to_i
+        expected_version = ENV.fetch("CI_VERSION")
+        expected_ios_build = Integer(ENV.fetch("CI_IOS_BUILD"))
+        expected_android_build = Integer(ENV.fetch("CI_ANDROID_BUILD"))
📝 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
pkg_version = get_current_version
ios_build = get_ios_build_number
android_build = get_android_build_number
expected_version = ENV["CI_VERSION"]
expected_ios_build = ENV["CI_IOS_BUILD"].to_i
expected_android_build = ENV["CI_ANDROID_BUILD"].to_i
pkg_version = get_current_version
ios_build = Integer(get_ios_build_number)
android_build = Integer(get_android_build_number)
expected_version = ENV.fetch("CI_VERSION")
expected_ios_build = Integer(ENV.fetch("CI_IOS_BUILD"))
expected_android_build = Integer(ENV.fetch("CI_ANDROID_BUILD"))
🤖 Prompt for AI Agents
In app/fastlane/helpers/version_manager.rb around lines 53 to 60, the code
currently reads ENV[...] and compares build numbers that may be strings; update
it to use ENV.fetch("CI_VERSION") and ENV.fetch("CI_IOS_BUILD").to_i /
ENV.fetch("CI_ANDROID_BUILD").to_i so missing CI vars raise clear errors, and
coerce the values returned by get_ios_build_number and get_android_build_number
to integers (e.g. .to_i) before comparison to avoid false mismatches when
version.json contains strings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
.github/workflows/mobile-deploy.yml (1)

1207-1210: Fix Ruby gems cache path for Android monitoring.

The monitoring step in the Android build job checks app/ios/vendor/bundle, but Android builds cache Ruby gems at app/vendor/bundle (as configured in line 958 and confirmed by learnings). This reports the wrong cache size.

Based on learnings

Apply this diff:

-          if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
-            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
+          if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
+            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
             echo "Ruby gems: $GEMS_SIZE"
           fi
🧹 Nitpick comments (2)
.github/workflows/mobile-deploy.yml (2)

236-238: Clarify path terminology in comment.

The comment states "Using absolute path" but ${{ env.APP_PATH }} expands to ${{ github.workspace }}/app, which is a fully-qualified path, not an absolute path in the traditional sense. While the path works correctly, the comment could be clearer.

Update the comment:

-          # Use version-manager script to calculate bump
-          # NOTE: Using absolute path to ensure script is found regardless of CWD
+          # Use version-manager script to calculate bump (with full path for reliability)
           node ${{ env.APP_PATH }}/scripts/version-manager.cjs bump "$VERSION_BUMP" "$PLATFORM"

1409-1460: Consider checking tag existence before attempting creation.

While the current approach handles errors gracefully, relying on git exit codes (code 128 for existing tags) is brittle and could change across git versions. The force push fallback at the end could also be risky if multiple workflows attempt to push simultaneously.

Consider checking tag existence first:

           echo "📦 Creating tags for version $VERSION"

           # Create main version tag (idempotent)
-          if git tag -a "v${VERSION}" -m "Release ${VERSION}" 2>/dev/null; then
-            echo "✅ Created tag: v${VERSION}"
-          else
-            EXIT_CODE=$?
-            if [ $EXIT_CODE -eq 128 ]; then
-              echo "⏭️  Tag v${VERSION} already exists"
-            else
-              echo "❌ Failed to create tag v${VERSION} with exit code $EXIT_CODE"
-              exit 1
-            fi
+          if git rev-parse "v${VERSION}" >/dev/null 2>&1; then
+            echo "⏭️  Tag v${VERSION} already exists"
+          else
+            git tag -a "v${VERSION}" -m "Release ${VERSION}"
+            echo "✅ Created tag: v${VERSION}"
           fi

Apply similar pattern to the platform-specific tags and use --force-with-lease for safer concurrent pushes:

-          # Push all tags (force to handle any conflicts)
-          if git push origin --tags 2>/dev/null; then
-            echo "🚀 Tags pushed to repository"
-          else
-            echo "⚠️  Some tags may already exist on remote, trying force push..."
-            git push origin --tags --force
-            echo "🚀 Tags force-pushed to repository"
-          fi
+          # Push tags (using force-with-lease for safety)
+          git push origin --tags --force-with-lease
+          echo "🚀 Tags pushed to repository"
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b4c937 and a66c590.

📒 Files selected for processing (7)
  • .github/actions/create-version-bump-pr/action.yml (1 hunks)
  • .github/actions/get-version/action.yml (1 hunks)
  • .github/workflows/mobile-deploy.yml (18 hunks)
  • .github/workflows/mobile-e2e.yml (2 hunks)
  • app/fastlane/Fastfile (6 hunks)
  • app/fastlane/helpers/version_manager.rb (1 hunks)
  • app/scripts/version-manager.cjs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.{yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/**/*.{yml,yaml}: In GitHub workflows, use the shared composite actions in .github/actions for dependency caching instead of calling actions/cache directly
Use the cache-yarn composite action for Yarn dependency caching in workflows
Use the cache-bundler composite action for Ruby gems caching in workflows
Use the cache-gradle composite action for Gradle caching in workflows
Use the cache-pods composite action for CocoaPods caching in workflows

Files:

  • .github/workflows/mobile-e2e.yml
  • .github/workflows/mobile-deploy.yml
🧠 Learnings (2)
📚 Learning: 2025-10-08T20:23:58.783Z
Learnt from: transphorm
PR: selfxyz/self#1244
File: .github/workflows/mobile-deploy.yml:774-776
Timestamp: 2025-10-08T20:23:58.783Z
Learning: In the selfxyz/self repository, for the mobile deployment workflow (.github/workflows/mobile-deploy.yml):
- iOS builds cache Ruby gems at `app/ios/vendor/bundle`
- Android builds cache Ruby gems at `app/vendor/bundle`
- These paths should be used consistently within their respective build jobs

Applied to files:

  • .github/workflows/mobile-deploy.yml
📚 Learning: 2025-10-04T05:29:43.587Z
Learnt from: CR
PR: selfxyz/self#0
File: AGENTS.md:0-0
Timestamp: 2025-10-04T05:29:43.587Z
Learning: Applies to .github/workflows/**/*.{yml,yaml} : Use the cache-bundler composite action for Ruby gems caching in workflows

Applied to files:

  • .github/workflows/mobile-deploy.yml
🪛 RuboCop (1.81.1)
app/fastlane/Fastfile

[warning] 136-136: Useless assignment to variable - version_bump.

(Lint/UselessAssignment)


[warning] 307-307: Useless assignment to variable - version_bump. Did you mean version_code?

(Lint/UselessAssignment)

app/fastlane/helpers/version_manager.rb

[convention] 47-78: Assignment Branch Condition size for verify_ci_version_match is too high. [<9, 20, 9> 23.71/23]

(Metrics/AbcSize)

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build-deps
  • GitHub Check: android-build-test
  • GitHub Check: e2e-ios
  • GitHub Check: analyze-android
  • GitHub Check: analyze-ios
🔇 Additional comments (6)
.github/workflows/mobile-e2e.yml (1)

37-37: Timeout increases look fine

Longer builds/e2e can justify 120m. No security or performance risk introduced.

Also applies to: 181-181

.github/actions/get-version/action.yml (1)

10-14: Good addition: expose version via outputs

Exporting to GITHUB_OUTPUT with a stable step id improves reuse; keeping GITHUB_ENV preserves backward compatibility.

Also applies to: 19-19, 23-23

app/fastlane/helpers/version_manager.rb (1)

47-78: CI version verification is solid

Strict env checks + explicit comparisons + clear error logs. This reduces release mistakes. No medium+ issues spotted.

app/fastlane/Fastfile (2)

98-102: Verification gate in CI is appropriate

Conditionally verifying CI-provided version/build before build is a good safety check.


175-184: Syncing build numbers from version.json is correct

Setting Xcode build number and Android versionCode from CI-managed data matches the new flow. iOS build-number verification present; Android verification is acknowledged and gated by permissions.

Also applies to: 332-340

.github/workflows/mobile-deploy.yml (1)

287-309: LGTM: Branch verification logic is solid.

The branch verification steps correctly handle both PR merges (building from base branch) and manual triggers (building from ref_name), with clear logging and defensive checks to detect potential issues.

Comment on lines +20 to +61
- name: Create version bump PR
shell: bash
run: |
BRANCH_NAME="ci/bump-${{ inputs.platform }}-build-${{ github.run_id }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Ensure we're on staging branch, not detached HEAD
git fetch origin staging dev
git checkout staging
# Check if staging has commits not in dev (version bumps + any build changes)
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
if [ "$COMMITS_AHEAD" -eq 0 ]; then
echo "ℹ️ No new commits on staging compared to dev. Skipping PR creation."
exit 0
fi
echo "📊 Staging is $COMMITS_AHEAD commit(s) ahead of dev"
# Create new branch from current staging (which has all version changes)
git checkout -b ${BRANCH_NAME}
# Push the branch
git push --set-upstream origin ${BRANCH_NAME}
# Determine PR title based on platform
if [ "${{ inputs.platform }}" = "mobile" ]; then
PR_TITLE="chore: bump mobile app version to ${{ inputs.version }}"
else
PR_TITLE="chore: bump ${{ inputs.platform }} build for ${{ inputs.version }}"
fi
gh pr create \
--base dev \
--head ${BRANCH_NAME} \
--title "$PR_TITLE" \
--body "Automated version bump by CI" \
--label "automated"
env:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden the script with bash strict mode; unused input is misleading

  • Without set -euo pipefail, errors in intermediary commands can slip by, creating or labeling an unintended PR.
  • inputs.file_paths is declared but unused; this is confusing and may imply partial-change PRs that the action doesn’t implement.

Apply this diff to harden the script:

     - name: Create version bump PR
       shell: bash
       run: |
+        set -euo pipefail
         BRANCH_NAME="ci/bump-${{ inputs.platform }}-build-${{ github.run_id }}"

And either remove the file_paths input from the action definition, or document it as unused. If you intend to restrict PRs to certain paths, implement staged commits based on the provided list before pushing.

📝 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
- name: Create version bump PR
shell: bash
run: |
BRANCH_NAME="ci/bump-${{ inputs.platform }}-build-${{ github.run_id }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Ensure we're on staging branch, not detached HEAD
git fetch origin staging dev
git checkout staging
# Check if staging has commits not in dev (version bumps + any build changes)
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
if [ "$COMMITS_AHEAD" -eq 0 ]; then
echo "ℹ️ No new commits on staging compared to dev. Skipping PR creation."
exit 0
fi
echo "📊 Staging is $COMMITS_AHEAD commit(s) ahead of dev"
# Create new branch from current staging (which has all version changes)
git checkout -b ${BRANCH_NAME}
# Push the branch
git push --set-upstream origin ${BRANCH_NAME}
# Determine PR title based on platform
if [ "${{ inputs.platform }}" = "mobile" ]; then
PR_TITLE="chore: bump mobile app version to ${{ inputs.version }}"
else
PR_TITLE="chore: bump ${{ inputs.platform }} build for ${{ inputs.version }}"
fi
gh pr create \
--base dev \
--head ${BRANCH_NAME} \
--title "$PR_TITLE" \
--body "Automated version bump by CI" \
--label "automated"
env:
- name: Create version bump PR
shell: bash
run: |
set -euo pipefail
BRANCH_NAME="ci/bump-${{ inputs.platform }}-build-${{ github.run_id }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Ensure we're on staging branch, not detached HEAD
git fetch origin staging dev
git checkout staging
# Check if staging has commits not in dev (version bumps + any build changes)
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
if [ "$COMMITS_AHEAD" -eq 0 ]; then
echo "ℹ️ No new commits on staging compared to dev. Skipping PR creation."
exit 0
fi
echo "📊 Staging is $COMMITS_AHEAD commit(s) ahead of dev"
# Create new branch from current staging (which has all version changes)
git checkout -b ${BRANCH_NAME}
# Push the branch
git push --set-upstream origin ${BRANCH_NAME}
# Determine PR title based on platform
if [ "${{ inputs.platform }}" = "mobile" ]; then
PR_TITLE="chore: bump mobile app version to ${{ inputs.version }}"
else
PR_TITLE="chore: bump ${{ inputs.platform }} build for ${{ inputs.version }}"
fi
gh pr create \
--base dev \
--head ${BRANCH_NAME} \
--title "$PR_TITLE" \
--body "Automated version bump by CI" \
--label "automated"
env:
🤖 Prompt for AI Agents
.github/actions/create-version-bump-pr/action.yml lines 20-61: the action's run
script lacks bash strict mode and declares an unused inputs.file_paths which is
misleading; enable strict mode (set -euo pipefail and IFS=$'\n\t') at the top of
the script and ensure variables are quoted to fail fast on errors, and then
either remove inputs.file_paths from the action.yml inputs or document it as
unused; if you intend to limit changes by path, implement staging/committing
only the listed paths before creating the branch and pushing.

Comment on lines +29 to +34
git fetch origin staging dev
git checkout staging
# Check if staging has commits not in dev (version bumps + any build changes)
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure you base the branch and ahead-count on origin/staging (avoid stale local branch)

Using a possibly stale local staging branch risks false “no commits” and wrong diffs. Reset to the remote ref and compute ahead against origin/staging.

Apply this diff:

-        git fetch origin staging dev
-        git checkout staging
+        git fetch origin staging dev --prune
+        # Ensure local 'staging' matches the remote ref exactly
+        git checkout -B staging origin/staging
@@
-        COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
+        COMMITS_AHEAD=$(git rev-list --count origin/dev..origin/staging)
@@
-        git checkout -b ${BRANCH_NAME}
+        git checkout -b "${BRANCH_NAME}"

Also applies to: 42-47

🤖 Prompt for AI Agents
In .github/actions/create-version-bump-pr/action.yml around lines 29 to 34 (and
similarly for lines 42 to 47), the script checks a possibly stale local staging
branch and computes commits-ahead against a local ref; update the workflow to
reset the local staging to the remote by fetching and checking out
origin/staging (or using git fetch origin staging && git checkout -B staging
origin/staging) and compute the ahead count against origin/staging (e.g., git
rev-list --count origin/dev..origin/staging) so the branch base and the
ahead-count are always computed from the remote refs rather than stale local
branches.

Comment on lines +791 to 793
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
echo "Ruby gems: $GEMS_SIZE"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Ruby gems cache path for iOS monitoring.

The monitoring step checks app/vendor/bundle but iOS builds cache Ruby gems at app/ios/vendor/bundle (as configured in line 359 and confirmed by learnings). This inconsistency means the cache size won't be reported correctly for iOS builds.

Based on learnings

Apply this diff:

-          if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
-            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
+          if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
+            GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
             echo "Ruby gems: $GEMS_SIZE"
           fi
📝 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
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
echo "Ruby gems: $GEMS_SIZE"
if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
echo "Ruby gems: $GEMS_SIZE"
fi
🤖 Prompt for AI Agents
In .github/workflows/mobile-deploy.yml around lines 791-793 the monitoring step
checks "${{ env.APP_PATH }}/vendor/bundle" but iOS builds cache gems at "${{
env.APP_PATH }}/ios/vendor/bundle"; update the check to look for the iOS path
(or implement a small conditional that checks "${{ env.APP_PATH
}}/ios/vendor/bundle" first and falls back to "${{ env.APP_PATH
}}/vendor/bundle"), then compute GEMS_SIZE from the existing directory and echo
the result so the iOS cache size is reported correctly.

Comment on lines +1250 to +1279
- name: Apply version bump from outputs
run: |
cd ${{ env.APP_PATH }}
# Get current version from package.json
CURRENT_VERSION=$(node -p "require('./package.json').version")
# Get new version from version.json (if it exists and has version field)
if [ -f version.json ] && grep -q '"version"' version.json; then
NEW_VERSION=$(node -pe 'require("./version.json").version' 2>/dev/null || echo "")
else
# Fallback: use current version from package.json
NEW_VERSION="$CURRENT_VERSION"
VERSION="${{ needs.bump-version.outputs.version }}"
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"
# Update package.json version
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '$VERSION';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
console.log('✅ Updated package.json');
"
# Update version.json build numbers
node -e "
const fs = require('fs');
const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
version.ios.build = $IOS_BUILD;
version.android.build = $ANDROID_BUILD;
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
console.log('✅ Updated version.json');
"
echo "✅ Versions applied successfully"
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use version-manager script instead of duplicating logic.

The inline Node.js code duplicates the logic already implemented in the version-manager.cjs apply command. This creates a maintenance burden—if the version management logic changes, both places must be updated.

Replace the inline code with a call to the version-manager script:

       - name: Apply version bump from outputs
         run: |
           cd ${{ env.APP_PATH }}

           VERSION="${{ needs.bump-version.outputs.version }}"
           IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
           ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"

           echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"

-          # Update package.json version
-          node -e "
-            const fs = require('fs');
-            const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
-            pkg.version = '$VERSION';
-            fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
-            console.log('✅ Updated package.json');
-          "
-
-          # Update version.json build numbers
-          node -e "
-            const fs = require('fs');
-            const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
-            version.ios.build = $IOS_BUILD;
-            version.android.build = $ANDROID_BUILD;
-            fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
-            console.log('✅ Updated version.json');
-          "
-
-          echo "✅ Versions applied successfully"
+          # Use version-manager script to apply versions
+          node scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
📝 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
- name: Apply version bump from outputs
run: |
cd ${{ env.APP_PATH }}
# Get current version from package.json
CURRENT_VERSION=$(node -p "require('./package.json').version")
# Get new version from version.json (if it exists and has version field)
if [ -f version.json ] && grep -q '"version"' version.json; then
NEW_VERSION=$(node -pe 'require("./version.json").version' 2>/dev/null || echo "")
else
# Fallback: use current version from package.json
NEW_VERSION="$CURRENT_VERSION"
VERSION="${{ needs.bump-version.outputs.version }}"
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"
# Update package.json version
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '$VERSION';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
console.log('✅ Updated package.json');
"
# Update version.json build numbers
node -e "
const fs = require('fs');
const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
version.ios.build = $IOS_BUILD;
version.android.build = $ANDROID_BUILD;
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
console.log('✅ Updated version.json');
"
echo "✅ Versions applied successfully"
- name: Apply version bump from outputs
run: |
cd ${{ env.APP_PATH }}
VERSION="${{ needs.bump-version.outputs.version }}"
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"
# Use version-manager script to apply versions
node scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
🤖 Prompt for AI Agents
In .github/workflows/mobile-deploy.yml around lines 1250 to 1279, the inline
Node.js blocks duplicate version-management logic; replace them with a single
call to the existing version-manager script. Change the step to cd to ${{
env.APP_PATH }} and invoke the version-manager command (node
./path/to/version-manager.cjs apply or the repository's equivalent) passing the
VERSION, IOS_BUILD and ANDROID_BUILD values (or exporting them to the script) so
it updates package.json and version.json; ensure the step fails on non-zero exit
from the script and prints a success message on completion.

Comment on lines +67 to +84
function writePackageJson(data) {
try {
fs.writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
} catch (error) {
throw new Error(`Failed to write package.json: ${error.message}`);
}
}

/**
* Write version.json
*/
function writeVersionJson(data) {
try {
fs.writeFileSync(VERSION_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
} catch (error) {
throw new Error(`Failed to write version.json: ${error.message}`);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add input validation before writing files.

The write functions accept any data without validation, which could corrupt the version files if invalid data is passed. This is especially risky since these files are the source of truth for deployments.

Consider adding validation helpers:

+/**
+ * Validate version string format (X.Y.Z)
+ */
+function isValidVersion(version) {
+  return /^\d+\.\d+\.\d+$/.test(version);
+}
+
+/**
+ * Validate build number (positive integer)
+ */
+function isValidBuild(build) {
+  return Number.isInteger(build) && build > 0;
+}
+
 /**
  * Write package.json
  */
 function writePackageJson(data) {
+  if (!data.version || !isValidVersion(data.version)) {
+    throw new Error(`Invalid version format: ${data.version}. Expected X.Y.Z`);
+  }
   try {
     fs.writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
   } catch (error) {
     throw new Error(`Failed to write package.json: ${error.message}`);
   }
 }

 /**
  * Write version.json
  */
 function writeVersionJson(data) {
+  if (!data?.ios?.build || !isValidBuild(data.ios.build)) {
+    throw new Error(`Invalid iOS build number: ${data?.ios?.build}`);
+  }
+  if (!data?.android?.build || !isValidBuild(data.android.build)) {
+    throw new Error(`Invalid Android build number: ${data?.android?.build}`);
+  }
   try {
     fs.writeFileSync(VERSION_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
   } catch (error) {
     throw new Error(`Failed to write version.json: ${error.message}`);
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/scripts/version-manager.cjs around lines 67 to 84, both writePackageJson
and writeVersionJson accept any input and write it straight to disk; add input
validation before writing by implementing small helpers that verify the data is
the expected type (object), contains required keys (for package.json: at minimum
name and version; for version.json: at minimum version and build/timestamp or
whatever project expects), and that version strings match SemVer (or your
project's version format); if validation fails, throw a clear error and do not
write the file; optionally run JSON.stringify on a sanitized/normalized object
(ensuring no functions/undefined) to guarantee deterministic output.

Comment on lines +89 to +100
function getVersionInfo() {
const pkg = readPackageJson();
const versionData = readVersionJson();

return {
version: pkg.version,
iosBuild: versionData.ios.build,
androidBuild: versionData.android.build,
iosLastDeployed: versionData.ios.lastDeployed,
androidLastDeployed: versionData.android.lastDeployed,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Validate JSON structure before accessing nested properties.

The function directly accesses versionData.ios.build and versionData.android.build without verifying the structure exists, which will produce cryptic errors like "Cannot read property 'build' of undefined" if the JSON is malformed.

Apply this diff to add structure validation:

 function getVersionInfo() {
   const pkg = readPackageJson();
   const versionData = readVersionJson();

+  // Validate structure
+  if (!pkg.version) {
+    throw new Error('package.json missing version field');
+  }
+  if (!versionData?.ios?.build || !versionData?.android?.build) {
+    throw new Error('version.json missing required ios.build or android.build fields');
+  }
+
   return {
     version: pkg.version,
     iosBuild: versionData.ios.build,
     androidBuild: versionData.android.build,
     iosLastDeployed: versionData.ios.lastDeployed,
     androidLastDeployed: versionData.android.lastDeployed,
   };
 }
📝 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
function getVersionInfo() {
const pkg = readPackageJson();
const versionData = readVersionJson();
return {
version: pkg.version,
iosBuild: versionData.ios.build,
androidBuild: versionData.android.build,
iosLastDeployed: versionData.ios.lastDeployed,
androidLastDeployed: versionData.android.lastDeployed,
};
}
function getVersionInfo() {
const pkg = readPackageJson();
const versionData = readVersionJson();
// Validate structure
if (!pkg.version) {
throw new Error('package.json missing version field');
}
if (!versionData?.ios?.build || !versionData?.android?.build) {
throw new Error('version.json missing required ios.build or android.build fields');
}
return {
version: pkg.version,
iosBuild: versionData.ios.build,
androidBuild: versionData.android.build,
iosLastDeployed: versionData.ios.lastDeployed,
androidLastDeployed: versionData.android.lastDeployed,
};
}
🤖 Prompt for AI Agents
In app/scripts/version-manager.cjs around lines 89 to 100, getVersionInfo
currently accesses versionData.ios.build and versionData.android.build directly;
add explicit validation of versionData and its ios/android objects before
reading nested properties and return safe defaults or throw a descriptive error
if the structure is invalid. Modify the function to (a) check that versionData
is an object, (b) check that versionData.ios and versionData.android exist and
are objects, and (c) read .build and .lastDeployed only after those checks (or
use optional chaining and default to null/undefined), and if validation fails,
throw or log a clear error mentioning malformed version JSON so callers don’t
receive cryptic "cannot read property" exceptions.

Comment on lines +145 to +201
function bumpVersion(bumpType, platform = 'both') {
const validBumpTypes = ['major', 'minor', 'patch', 'build'];
const validPlatforms = ['ios', 'android', 'both'];

if (!validBumpTypes.includes(bumpType)) {
throw new Error(
`Invalid bump type: ${bumpType}. Expected: ${validBumpTypes.join(', ')}`,
);
}

if (!validPlatforms.includes(platform)) {
throw new Error(
`Invalid platform: ${platform}. Expected: ${validPlatforms.join(', ')}`,
);
}

const pkg = readPackageJson();
const versionData = readVersionJson();

let newVersion = pkg.version;

// Bump semantic version if major/minor/patch
if (bumpType !== 'build') {
newVersion = bumpSemanticVersion(pkg.version, bumpType);
console.log(
`📦 Bumping ${bumpType} version: ${pkg.version}${newVersion}`,
);
} else {
console.log(`📦 Keeping version: ${newVersion} (build-only bump)`);
}

// Bump build numbers based on platform
let newIosBuild = versionData.ios.build;
let newAndroidBuild = versionData.android.build;

if (platform === 'ios' || platform === 'both') {
newIosBuild += 1;
console.log(`🍎 iOS build: ${versionData.ios.build}${newIosBuild}`);
} else {
console.log(`🍎 iOS build: ${newIosBuild} (unchanged)`);
}

if (platform === 'android' || platform === 'both') {
newAndroidBuild += 1;
console.log(
`🤖 Android build: ${versionData.android.build}${newAndroidBuild}`,
);
} else {
console.log(`🤖 Android build: ${newAndroidBuild} (unchanged)`);
}

return {
version: newVersion,
iosBuild: newIosBuild,
androidBuild: newAndroidBuild,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Validate build numbers are integers before arithmetic operations.

The function increments build numbers with += 1 without verifying they're numeric, which could produce NaN or unexpected results if the JSON is corrupted with string values like "180" or invalid data.

Add validation at the start of the function:

 function bumpVersion(bumpType, platform = 'both') {
   const validBumpTypes = ['major', 'minor', 'patch', 'build'];
   const validPlatforms = ['ios', 'android', 'both'];

   if (!validBumpTypes.includes(bumpType)) {
     throw new Error(
       `Invalid bump type: ${bumpType}. Expected: ${validBumpTypes.join(', ')}`,
     );
   }

   if (!validPlatforms.includes(platform)) {
     throw new Error(
       `Invalid platform: ${platform}. Expected: ${validPlatforms.join(', ')}`,
     );
   }

   const pkg = readPackageJson();
   const versionData = readVersionJson();

+  // Validate build numbers are positive integers
+  if (!Number.isInteger(versionData.ios.build) || versionData.ios.build < 1) {
+    throw new Error(`Invalid iOS build number: ${versionData.ios.build}. Expected positive integer.`);
+  }
+  if (!Number.isInteger(versionData.android.build) || versionData.android.build < 1) {
+    throw new Error(`Invalid Android build number: ${versionData.android.build}. Expected positive integer.`);
+  }
+
   let newVersion = pkg.version;

   // Bump semantic version if major/minor/patch
   if (bumpType !== 'build') {
     newVersion = bumpSemanticVersion(pkg.version, bumpType);
📝 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
function bumpVersion(bumpType, platform = 'both') {
const validBumpTypes = ['major', 'minor', 'patch', 'build'];
const validPlatforms = ['ios', 'android', 'both'];
if (!validBumpTypes.includes(bumpType)) {
throw new Error(
`Invalid bump type: ${bumpType}. Expected: ${validBumpTypes.join(', ')}`,
);
}
if (!validPlatforms.includes(platform)) {
throw new Error(
`Invalid platform: ${platform}. Expected: ${validPlatforms.join(', ')}`,
);
}
const pkg = readPackageJson();
const versionData = readVersionJson();
let newVersion = pkg.version;
// Bump semantic version if major/minor/patch
if (bumpType !== 'build') {
newVersion = bumpSemanticVersion(pkg.version, bumpType);
console.log(
`📦 Bumping ${bumpType} version: ${pkg.version}${newVersion}`,
);
} else {
console.log(`📦 Keeping version: ${newVersion} (build-only bump)`);
}
// Bump build numbers based on platform
let newIosBuild = versionData.ios.build;
let newAndroidBuild = versionData.android.build;
if (platform === 'ios' || platform === 'both') {
newIosBuild += 1;
console.log(`🍎 iOS build: ${versionData.ios.build}${newIosBuild}`);
} else {
console.log(`🍎 iOS build: ${newIosBuild} (unchanged)`);
}
if (platform === 'android' || platform === 'both') {
newAndroidBuild += 1;
console.log(
`🤖 Android build: ${versionData.android.build}${newAndroidBuild}`,
);
} else {
console.log(`🤖 Android build: ${newAndroidBuild} (unchanged)`);
}
return {
version: newVersion,
iosBuild: newIosBuild,
androidBuild: newAndroidBuild,
};
}
function bumpVersion(bumpType, platform = 'both') {
const validBumpTypes = ['major', 'minor', 'patch', 'build'];
const validPlatforms = ['ios', 'android', 'both'];
if (!validBumpTypes.includes(bumpType)) {
throw new Error(
`Invalid bump type: ${bumpType}. Expected: ${validBumpTypes.join(', ')}`,
);
}
if (!validPlatforms.includes(platform)) {
throw new Error(
`Invalid platform: ${platform}. Expected: ${validPlatforms.join(', ')}`,
);
}
const pkg = readPackageJson();
const versionData = readVersionJson();
// Validate build numbers are positive integers
if (!Number.isInteger(versionData.ios.build) || versionData.ios.build < 1) {
throw new Error(
`Invalid iOS build number: ${versionData.ios.build}. Expected positive integer.`
);
}
if (!Number.isInteger(versionData.android.build) || versionData.android.build < 1) {
throw new Error(
`Invalid Android build number: ${versionData.android.build}. Expected positive integer.`
);
}
let newVersion = pkg.version;
// Bump semantic version if major/minor/patch
if (bumpType !== 'build') {
newVersion = bumpSemanticVersion(pkg.version, bumpType);
console.log(
`📦 Bumping ${bumpType} version: ${pkg.version}${newVersion}`,
);
} else {
console.log(`📦 Keeping version: ${newVersion} (build-only bump)`);
}
// Bump build numbers based on platform
let newIosBuild = versionData.ios.build;
let newAndroidBuild = versionData.android.build;
if (platform === 'ios' || platform === 'both') {
newIosBuild += 1;
console.log(`🍎 iOS build: ${versionData.ios.build}${newIosBuild}`);
} else {
console.log(`🍎 iOS build: ${newIosBuild} (unchanged)`);
}
if (platform === 'android' || platform === 'both') {
newAndroidBuild += 1;
console.log(
`🤖 Android build: ${versionData.android.build}${newAndroidBuild}`,
);
} else {
console.log(`🤖 Android build: ${newAndroidBuild} (unchanged)`);
}
return {
version: newVersion,
iosBuild: newIosBuild,
androidBuild: newAndroidBuild,
};
}
🤖 Prompt for AI Agents
In app/scripts/version-manager.cjs around lines 145 to 201, the code increments
ios/android build numbers without validating they are numeric, which can produce
NaN if the JSON contains strings or invalid data; before doing arithmetic, read
versionData.ios.build and versionData.android.build, coerce each to an integer
(e.g., parseInt or Number) and verify Number.isInteger and >= 0, fallback to 0
or throw a clear Error if invalid, then perform the +1 operations on the
validated integers and use those validated values in logs and the returned
object.

Comment on lines +206 to +224
function applyVersions(version, iosBuild, androidBuild) {
console.log(`📝 Applying versions to files...`);
console.log(` Version: ${version}`);
console.log(` iOS Build: ${iosBuild}`);
console.log(` Android Build: ${androidBuild}`);

// Update package.json
const pkg = readPackageJson();
pkg.version = version;
writePackageJson(pkg);
console.log(`✅ Updated package.json`);

// Update version.json
const versionData = readVersionJson();
versionData.ios.build = iosBuild;
versionData.android.build = androidBuild;
writeVersionJson(versionData);
console.log(`✅ Updated version.json`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Validate function parameters before applying changes.

The function accepts version and build parameters without validation, which could write invalid data to the files if called with incorrect arguments (e.g., from a buggy workflow step or malicious input).

Add parameter validation:

 function applyVersions(version, iosBuild, androidBuild) {
+  // Validate inputs
+  if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) {
+    throw new Error(`Invalid version format: ${version}. Expected X.Y.Z`);
+  }
+  if (!Number.isInteger(iosBuild) || iosBuild < 1) {
+    throw new Error(`Invalid iOS build number: ${iosBuild}. Expected positive integer.`);
+  }
+  if (!Number.isInteger(androidBuild) || androidBuild < 1) {
+    throw new Error(`Invalid Android build number: ${androidBuild}. Expected positive integer.`);
+  }
+
   console.log(`📝 Applying versions to files...`);
   console.log(`   Version: ${version}`);
   console.log(`   iOS Build: ${iosBuild}`);
   console.log(`   Android Build: ${androidBuild}`);
📝 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
function applyVersions(version, iosBuild, androidBuild) {
console.log(`📝 Applying versions to files...`);
console.log(` Version: ${version}`);
console.log(` iOS Build: ${iosBuild}`);
console.log(` Android Build: ${androidBuild}`);
// Update package.json
const pkg = readPackageJson();
pkg.version = version;
writePackageJson(pkg);
console.log(`✅ Updated package.json`);
// Update version.json
const versionData = readVersionJson();
versionData.ios.build = iosBuild;
versionData.android.build = androidBuild;
writeVersionJson(versionData);
console.log(`✅ Updated version.json`);
}
function applyVersions(version, iosBuild, androidBuild) {
// Validate inputs
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) {
throw new Error(`Invalid version format: ${version}. Expected X.Y.Z`);
}
if (!Number.isInteger(iosBuild) || iosBuild < 1) {
throw new Error(`Invalid iOS build number: ${iosBuild}. Expected positive integer.`);
}
if (!Number.isInteger(androidBuild) || androidBuild < 1) {
throw new Error(`Invalid Android build number: ${androidBuild}. Expected positive integer.`);
}
console.log(`📝 Applying versions to files...`);
console.log(` Version: ${version}`);
console.log(` iOS Build: ${iosBuild}`);
console.log(` Android Build: ${androidBuild}`);
// Update package.json
const pkg = readPackageJson();
pkg.version = version;
writePackageJson(pkg);
console.log(`✅ Updated package.json`);
// Update version.json
const versionData = readVersionJson();
versionData.ios.build = iosBuild;
versionData.android.build = androidBuild;
writeVersionJson(versionData);
console.log(`✅ Updated version.json`);
}
🤖 Prompt for AI Agents
In app/scripts/version-manager.cjs around lines 206 to 224, the applyVersions
function currently writes package.json and version.json without validating
inputs; add parameter validation at the top of the function to prevent invalid
writes: verify version is a non-empty string matching a semver pattern (e.g.
/^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$/) and that iosBuild and androidBuild are
present and are non-negative integers (or numeric strings that parse to
integers); if validation fails, log a clear error and throw or exit without
modifying any files; after validation, coerce build values to integers before
assigning them and optionally ensure versionData.ios and versionData.android
objects exist before setting build fields.

@transphorm transphorm merged commit 756a850 into staging Oct 10, 2025
36 checks passed
@transphorm transphorm deleted the justin/test-mobile-deploy-auto-pr branch October 10, 2025 18:21
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.

2 participants