Skip to main content

CodePush updates with Bitrise CI

Abstract

You can release your CodePush updates via a Bitrise CI Workflow that creates the update packages for both iOS and Android, and updates both to the Bitrise CodePush Server.

You can release your CodePush updates via a Bitrise CI Workflow. The process requires:

  • Setting up CodePush credentials: create Secrets and Environment Variables that hold the deployment ID and the deployment key from Bitrise CodePush.

  • Creating a Workflow that creates the update packages for both iOS and Android, and updates both to the Bitrise CodePush Server.

Configuring CodePush credentials on Bitrise

  1. Get your credentials: you need the Release Management app IDs and the CodePush deployment IDs and deployment keys.

  2. Create Environment Variables for the deployment IDs and the connected app IDs:

    • IOS_PROD_DEPLOYMENT_ID: The deployment ID for the iOS app.

    • ANDROID_PROD_DEPLOYMENT_ID: The deployment ID for the Android app.

    • IOS_CONNECTED_APP_ID: The app ID for the iOS app.

    • ANDROID_CONNECTED_APP_ID: The app ID for the iOS app.

  3. Create Secrets for the deployment key and the API token:

    • IOS_PROD_DEPLOYMENT_KEY: The deployment key of the iOS deployment from Bitrise CodePush.

    • ANDROID_PROD_DEPLOYMENT_KEY: The deployment key of the Android deployment from Bitrise CodePush.

    • BITRISE_API_TOKEN: Your personal access token or workspace API token.

Creating a Workflow for CodePush updates

This section shows you how to create a CI Workflow that will:

  • Create an update package for iOS.

  • Upload the iOS update package to Bitrise CodePush Server.

  • Create an update package for Android.

  • Upload the Android update package to Bitrise CodePush Server.

React Native app

Expo app

  1. Create a Workflow and after the git-clone Step, add an npm Step with the install command:

    ---
    format_version: '13'
    default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
    project_type: ios
    workflows:
      codepush_update_to_server:
        description: The workflow will generate and push your update to CodePush Server.
        steps:
        - git-clone@8: {}
        - npm@1:
            inputs:
            - command: install
  2. Add a script Step that extracts the app version using a JSON parser and set version in an Environment Variable using envman:

    - script@1:
        title: Extract App Version
        inputs:
        - content: |-
            #!/bin/bash
            set -e
    
            # Extract version from app.json using jq (JSON parser)
            VERSION=$(jq -r '.version' package.json)
    
            # Verify version extraction
            if [ -z "$VERSION" ]; then
              echo "Error: Could not extract version from package.json"
              exit 1
            fi
    
            echo "Extracted version from package.json: $VERSION"
    
            # Set the environment variable using envman
            envman add --key APP_VERSION --value "$VERSION"
    
            echo "Successfully set APP_VERSION=$VERSION"
  3. Add another script Step that clones the release-management-recipes repository from GitHub. This contains the upload script we'll use later.

    - script@1:
        title: Get Release Management Recipes
        inputs:
        - content: >-
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a
            non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            # write your script here
            git clone https://github.com/bitrise-io/release-management-recipes
    
  4. Generate your iOS update bundle and create a zip archive from it:

    - script@1:
        title: Generate iOS Update Bundle
        inputs:
        - content: >-
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a
            non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            npx react-native bundle \
              --platform ios \
              --dev false \
              --entry-file index.js \
              --bundle-output ./build/main.jsbundle \
              --assets-dest ./build
            
            # Create zip archive
            zip -r update.zip ./build
  5. Upload the iOS update bundle to the CodePush Server with upload_code_push_package.sh:

    - script@1:
        title: Upload iOS Update Bundle to Codepush Server
        inputs:
        - content: >
            #!/usr/bin/env bash
            # fail if any commands fails 
            set -e 
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all
            commands exit successfully 
            set -o pipefail 
            # debug log 
            set -x
    
            cd release-management-recipes
    
            UPLOAD_RESPONSE=$(PACKAGE_PATH=../update.zip \
                            AUTHORIZATION_TOKEN=$BITRISE_API_TOKEN \
                            CONNECTED_APP_ID=$IOS_CONNECTED_APP_ID \
                            DEPLOYMENT_ID=$IOS_PROD_DEPLOYMENT_ID \
                            APP_VERSION=$APP_VERSION /bin/bash ./api/upload_code_push_package.sh 2>&1)
            EXIT_CODE=$?
    
            if [ $EXIT_CODE -ne 0 ]; then 
            \  echo \"❌ upload_code_push_package.sh failed with exit code
            $EXIT_CODE\"
            \  echo \"$UPLOAD_RESPONSE\"
            \  exit $EXIT_CODE
            fi
    
            # Take only the last line of the response (final/latest status JSON
            object) FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
    
            # Check explicitly for ERR_INTERNAL or other internal error indicators
            if echo \"$UPLOAD_RESPONSE\" | grep -q \"ERR_INTERNAL\"; then
            \  ERROR_MESSAGE=$(echo \"$FINAL_RESPONSE\" | jq -r '.message' ||
            echo \"Unknown error\")
            \  echo \"❌ Server returned internal error: $ERROR_MESSAGE\"
            \  exit 1
            fi
    
    
            # Now safely parse 'status' and 'status_reason' from the final
            response line PACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r
            '.status' || echo "null") STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq
            -r '.status_reason' || echo "")
    
            if [ "$PACKAGE_STATUS" = "processed_valid" ]; then   echo "✅ Package
            uploaded and processed successfully."   rm -rf ../build.zip    rm -rf
            ../build else   echo "⚠️ Package upload unexpected status:
            $PACKAGE_STATUS - Reason: $STATUS_REASON"   exit 1 fi cd ..
    
  6. Generate the Android update bundle:

    - script@1:
        title: Generate Android Update Bundle
        inputs:
        - content: |
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            npx react-native bundle \
              --platform android \
              --dev false \
              --entry-file index.js \
              --bundle-output ./build/index.android.bundle \
              --assets-dest ./build
    
            # Create zip archive
            zip -r update.zip ./build
  7. Upload the Android update bundle to the CodePush Server:

    - script@1:
        title: Upload Android Update Bundle to Codepush Server
        inputs:
        - content: |
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            cd release-management-recipes
    
            UPLOAD_RESPONSE=$(PACKAGE_PATH=../update.zip \
                            AUTHORIZATION_TOKEN=$BITRISE_API_TOKEN \
                            CONNECTED_APP_ID=$ANDROID_CONNECTED_APP_ID \
                            DEPLOYMENT_ID=$ANDROID_PROD_DEPLOYMENT_ID \
                            APP_VERSION=$APP_VERSION /bin/bash ./api/upload_code_push_package.sh 2>&1)
            EXIT_CODE=$?
    
            if [ $EXIT_CODE -ne 0 ]; then
              echo "❌ upload_code_push_package.sh failed with exit code $EXIT_CODE"
              echo "$UPLOAD_RESPONSE"
              exit $EXIT_CODE
            fi
    
            # Take only the last line of the response (final/latest status JSON object)
            FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
    
            # Check explicitly for ERR_INTERNAL or other internal error indicators
            if echo "$UPLOAD_RESPONSE" | grep -q "ERR_INTERNAL"; then
              ERROR_MESSAGE=$(echo "$FINAL_RESPONSE" | jq -r '.message' || echo "Unknown error")
              echo "❌ Server returned internal error: $ERROR_MESSAGE"
              exit 1
            fi
    
            # Now safely parse 'status' and 'status_reason' from the final response line
            PACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r '.status' || echo "null")
            STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq -r '.status_reason' || echo "")
    
            if [ "$PACKAGE_STATUS" = "processed_valid" ]; then
              echo "✅ Package uploaded and processed successfully."
            else
              echo "⚠️ Package upload unexpected status: $PACKAGE_STATUS - Reason: $STATUS_REASON"
              exit 1
            fi
    
            cd ..
    
  1. Create a Workflow and after the git-clone Step, add an npm Step with the install command:

    ---
    format_version: '13'
    default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
    project_type: ios
    workflows:
      codepush_update_deploy:    
        description: |      
          Uploads Update Bundle to Bitrise CodePush Server    
        status_report_name: Executing <target_id> for <project_title>
        steps:
        - git-clone@8: {}
        - restore-npm-cache@2: {}
        - npm@1:
            inputs:
            - command: install
  2. Add a script Step that extracts the app version using awk and sets it as an Enviromment Variable:

    - script@1:
        title: Extract App Version
        inputs:
        - content: |-
            #!/bin/bash
            set -e
    
            # Simpler version using awk
            APP_VERSION=$(awk -F'"' '/version:/ {print $2}' app.config.js)
    
            # Check if the version was successfully extracted
            if [ -z "$APP_VERSION" ]; then
              echo "Error: Failed to extract version from app.config.js"
              exit 1
            fi
    
            echo "Extracted version: $APP_VERSION"
    
            # Set the environment variable using envman
            envman add --key APP_VERSION --value "$APP_VERSION"
    
            echo "Successfully set APP_VERSION=$APP_VERSION"
  3. Add another script Step that clones the release-management-recipes repository from GitHub. This contains the upload script we'll use later.

    - script@1:
        title: Get Release Management Recipes
        inputs:
        - content: >-
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a
            non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            # write your script here
            git clone https://github.com/bitrise-io/release-management-recipes
    
  4. Generate your iOS update bundle and create a zip archive from it:

    - script@1:
        title: Generate iOS Update Bundle
        inputs:
        - content: |-
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            # write your script here
            npx expo export:embed \
              --entry-file index.js \
              --platform ios \
              --dev false \
              --reset-cache \
              --bundle-output ./build/main.jsbundle \
              --assets-dest ./build \
              --minify false
    
            # Create zip archive
            zip -r update.zip ./build
    
  5. Upload the iOS update bundle to the CodePush Server with upload_code_push_package.sh:

    - script@1:
        title: Upload iOS Update Bundle to Codepush Server
        inputs:
        - content: >
            #!/usr/bin/env bash
            # fail if any commands fails 
            set -e 
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all
            commands exit successfully 
            set -o pipefail 
            # debug log 
            set -x
    
            cd release-management-recipes
    
            UPLOAD_RESPONSE=$(PACKAGE_PATH=../update.zip \
                            AUTHORIZATION_TOKEN=$BITRISE_API_TOKEN \
                            CONNECTED_APP_ID=$IOS_CONNECTED_APP_ID \
                            DEPLOYMENT_ID=$IOS_PROD_DEPLOYMENT_ID \
                            APP_VERSION=$APP_VERSION /bin/bash ./api/upload_code_push_package.sh 2>&1)
    
            EXIT_CODE=$?
    
            if [ $EXIT_CODE -ne 0 ]; then 
            \  echo \"❌ upload_code_push_package.sh failed with exit code
            $EXIT_CODE\"
            \  echo \"$UPLOAD_RESPONSE\"
            \  exit $EXIT_CODE
            fi
    
            # Take only the last line of the response (final/latest status JSON
            object) FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
    
            # Check explicitly for ERR_INTERNAL or other internal error indicators
            if echo \"$UPLOAD_RESPONSE\" | grep -q \"ERR_INTERNAL\"; then
            \  ERROR_MESSAGE=$(echo \"$FINAL_RESPONSE\" | jq -r '.message' ||
            echo \"Unknown error\")
            \  echo \"❌ Server returned internal error: $ERROR_MESSAGE\"
            \  exit 1
            fi
    
    
            # Now safely parse 'status' and 'status_reason' from the final
            response line PACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r
            '.status' || echo "null") STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq
            -r '.status_reason' || echo "")
    
            if [ "$PACKAGE_STATUS" = "processed_valid" ]; then   echo "✅ Package
            uploaded and processed successfully."   rm -rf ../build.zip    rm -rf
            ../build else   echo "⚠️ Package upload unexpected status:
            $PACKAGE_STATUS - Reason: $STATUS_REASON"   exit 1 fi cd ..
    
  6. Generate the Android update bundle:

    - script@1:
        title: Generate Android Update Bundle
        inputs:
        - content: |
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            # write your script here
            npx expo export:embed \
              --entry-file index.js \
              --platform android \
              --dev false \
              --reset-cache \
              --bundle-output ./build/index.android.bundle \
              --assets-dest ./build \
              --minify false
    
            # Create zip archive
            zip -r update.zip ./build
  7. Upload the Android update bundle to the CodePush Server:

    - script@1:
        title: Upload Android Update Bundle to Codepush Server
        inputs:
        - content: |
            #!/usr/bin/env bash
            # fail if any commands fails
            set -e
            # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
            set -o pipefail
            # debug log
            set -x
    
            cd release-management-recipes
    
            UPLOAD_RESPONSE=$(PACKAGE_PATH=../update.zip \
                            AUTHORIZATION_TOKEN=$BITRISE_API_TOKEN \
                            CONNECTED_APP_ID=$ANDROID_CONNECTED_APP_ID \
                            DEPLOYMENT_ID=$ANDROID_PROD_DEPLOYMENT_ID \
                            APP_VERSION=$APP_VERSION /bin/bash ./api/upload_code_push_package.sh 2>&1)
            EXIT_CODE=$?
    
            if [ $EXIT_CODE -ne 0 ]; then
              echo "❌ upload_code_push_package.sh failed with exit code $EXIT_CODE"
              echo "$UPLOAD_RESPONSE"
              exit $EXIT_CODE
            fi
    
            # Take only the last line of the response (final/latest status JSON object)
            FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
    
            # Check explicitly for ERR_INTERNAL or other internal error indicators
            if echo "$UPLOAD_RESPONSE" | grep -q "ERR_INTERNAL"; then
              ERROR_MESSAGE=$(echo "$FINAL_RESPONSE" | jq -r '.message' || echo "Unknown error")
              echo "❌ Server returned internal error: $ERROR_MESSAGE"
              exit 1
            fi
    
            # Now safely parse 'status' and 'status_reason' from the final response line
            PACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r '.status' || echo "null")
            STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq -r '.status_reason' || echo "")
    
            if [ "$PACKAGE_STATUS" = "processed_valid" ]; then
              echo "✅ Package uploaded and processed successfully."
            else
              echo "⚠️ Package upload unexpected status: $PACKAGE_STATUS - Reason: $STATUS_REASON"
              exit 1
            fi
    
            cd ..
    

Automating the CodePush update release

After creating a Workflow to generate and upload your CodePush updates, you have two ways of running it:

You can set up several different types of triggers to automate the process. Our example configuration uses a pull request trigger with a label:

Example 1. Release a CodePush update when a pull request is opened to a specific branch

In this example, we're setting up a trigger where:

  • Bitrise looks for pull requests opened with updates as the target branch.

  • The codepush_update_deploy Workflow is triggered when a pull request to the branch receives the release-update label to the PR.

  1. Create the trigger: set up a pull request trigger with two conditions, target_branch and label:

    • Set target_branch to updates.

    • Set label to release-update.

    GUI config

    You can set these up on the GUI of the Workflow Editor, too. Here we're showing you the YAML configuration.

    format_version: "13"
    default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
    project_type: react-native
    workflows:
      codepush_update_deploy:
        status_report_name: 'Executing <target_id> for <project_title>'
        description: |
          Uploads Update Bundle to Bitrise CodePush Server
        steps:
        [...]
        triggers:
          pull_request:
          - target_branch: updates
            label: release-update
    
  2. When you want to release CodePush updates, open a pull request to the updates branch.

  3. After the PR has been reviewed and approved, add the release-update label to it.

If all goes well, adding the label to the PR will trigger the codepush_update_deploy Workflow.