CodePush updates with Bitrise CI
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
-
Get your credentials: you need the Release Management app IDs and the CodePush deployment IDs and deployment keys.
-
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.
-
-
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
-
Create a Workflow and after the
git-cloneStep, add annpmStep with theinstallcommand:--- 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 -
Add a
scriptStep that extracts the app version using a JSON parser and set version in an Environment Variable usingenvman:- 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" -
Add another
scriptStep that clones therelease-management-recipesrepository 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 -
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 -
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 .. -
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 -
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 ..
-
Create a Workflow and after the
git-cloneStep, add annpmStep with theinstallcommand:--- 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 -
Add a
scriptStep that extracts the app version usingawkand 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" -
Add another
scriptStep that clones therelease-management-recipesrepository 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 -
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 -
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 .. -
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 -
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:
-
Triggering a build automatically when a defined condition is met. For the available conditions, see Supported trigger conditions.
You can set up several different types of triggers to automate the process. Our example configuration uses a pull request trigger with a label:
In this example, we're setting up a trigger where:
-
Bitrise looks for pull requests opened with
updatesas the target branch. -
The
codepush_update_deployWorkflow is triggered when a pull request to the branch receives therelease-updatelabel to the PR.
-
Create the trigger: set up a pull request trigger with two conditions,
target_branchandlabel:-
Set
target_branchtoupdates. -
Set
labeltorelease-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 -
-
When you want to release CodePush updates, open a pull request to the
updatesbranch. -
After the PR has been reviewed and approved, add the
release-updatelabel to it.
If all goes well, adding the label to the PR will trigger the codepush_update_deploy Workflow.