CodePush updates with Bitrise CI
You can release your CodePush updates via a Bitrise CI Workflow. The process requires:
- Setting up CodePush credentials: create Environment Variables and a Secret that hold the app IDs, the deployment IDs, the target versions, and a Bitrise API token.
- Creating a Workflow that creates the update packages for both iOS and Android, and uploads both to Bitrise.
Configuring CodePush credentials on BitriseClick to copy link
-
Get your credentials: you need the Release Management app IDs and the CodePush deployment IDs.
-
Create Environment Variables for the deployment IDs, the connected app IDs, and the target versions:
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 Android app.APP_TARGET_VERSIONS: The app versions that receive the update. Use an exact version or a range expression, for example1.2.0or>=1.5.0.
Bitrise cannot check whether your update works with the native code of an app version. You decide which versions are compatible, so set
APP_TARGET_VERSIONSyourself before each release. -
Create a Secret for the API token:
BITRISE_API_TOKEN: Your personal access token or workspace API token.
Creating a Workflow for CodePush updatesClick to copy link
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.
- Create an update package for Android.
- Upload the Android update package to Bitrise.
- 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.gitproject_type: react-nativeworkflows:codepush_update_deploy:description: Generates your update and pushes it to Bitrise.steps:- git-clone@8: {}- npm@1:inputs:- command: install -
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 Recipesinputs:- content: >-#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with anon-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# write your script heregit 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 Bundleinputs:- content: >-#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with anon-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# remove output of earlier runs so the zip holds one platform onlyrm -rf ./build update.zipnpx react-native bundle \--platform ios \--dev false \--entry-file index.js \--bundle-output ./build/main.jsbundle \--assets-dest ./build# Create zip archivezip -r update.zip ./build -
Upload the iOS update bundle to Bitrise with
upload_code_push_package.sh:- script@1:title: Upload iOS Update Bundle to Bitriseinputs:- content: >#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if allcommands exit successfullyset -o pipefail# debug logset -xcd release-management-recipesUPLOAD_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_TARGET_VERSIONS /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_CODEfi# Take only the last line of the response (final/latest status JSONobject) FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)# Check explicitly for ERR_INTERNAL or other internal error indicatorsif 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 1fi# Now safely parse 'status' and 'status_reason' from the finalresponse 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 "✅ Packageuploaded 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 Bundleinputs:- content: |#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# remove output of earlier runs so the zip holds one platform onlyrm -rf ./build update.zipnpx react-native bundle \--platform android \--dev false \--entry-file index.js \--bundle-output ./build/index.android.bundle \--assets-dest ./build# Create zip archivezip -r update.zip ./build -
Upload the Android update bundle to Bitrise:
- script@1:title: Upload Android Update Bundle to Bitriseinputs:- content: |#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -xcd release-management-recipesUPLOAD_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_TARGET_VERSIONS /bin/bash ./api/upload_code_push_package.sh 2>&1)EXIT_CODE=$?if [ $EXIT_CODE -ne 0 ]; thenecho "❌ upload_code_push_package.sh failed with exit code $EXIT_CODE"echo "$UPLOAD_RESPONSE"exit $EXIT_CODEfi# 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 indicatorsif echo "$UPLOAD_RESPONSE" | grep -q "ERR_INTERNAL"; thenERROR_MESSAGE=$(echo "$FINAL_RESPONSE" | jq -r '.message' || echo "Unknown error")echo "❌ Server returned internal error: $ERROR_MESSAGE"exit 1fi# Now safely parse 'status' and 'status_reason' from the final response linePACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r '.status' || echo "null")STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq -r '.status_reason' || echo "")if [ "$PACKAGE_STATUS" = "processed_valid" ]; thenecho "✅ Package uploaded and processed successfully."elseecho "⚠️ Package upload unexpected status: $PACKAGE_STATUS - Reason: $STATUS_REASON"exit 1ficd ..
-
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.gitproject_type: react-nativeworkflows:codepush_update_deploy:description: |Uploads Update Bundle to Bitrisestatus_report_name: Executing <target_id> for <project_title>steps:- git-clone@8: {}- restore-npm-cache@2: {}- npm@1:inputs:- command: install -
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 Recipesinputs:- content: >-#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with anon-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# write your script heregit 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 Bundleinputs:- content: |-#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# remove output of earlier runs so the zip holds one platform onlyrm -rf ./build update.zipnpx 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 archivezip -r update.zip ./build -
Upload the iOS update bundle to Bitrise with
upload_code_push_package.sh:- script@1:title: Upload iOS Update Bundle to Bitriseinputs:- content: >#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if allcommands exit successfullyset -o pipefail# debug logset -xcd release-management-recipesUPLOAD_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_TARGET_VERSIONS /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_CODEfi# Take only the last line of the response (final/latest status JSONobject) FINAL_RESPONSE=$(echo "$UPLOAD_RESPONSE" | tail -n1)# Check explicitly for ERR_INTERNAL or other internal error indicatorsif 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 1fi# Now safely parse 'status' and 'status_reason' from the finalresponse 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 "✅ Packageuploaded 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 Bundleinputs:- content: |#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -x# remove output of earlier runs so the zip holds one platform onlyrm -rf ./build update.zipnpx 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 archivezip -r update.zip ./build -
Upload the Android update bundle to Bitrise:
- script@1:title: Upload Android Update Bundle to Bitriseinputs:- content: |#!/usr/bin/env bash# fail if any commands failsset -e# make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfullyset -o pipefail# debug logset -xcd release-management-recipesUPLOAD_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_TARGET_VERSIONS /bin/bash ./api/upload_code_push_package.sh 2>&1)EXIT_CODE=$?if [ $EXIT_CODE -ne 0 ]; thenecho "❌ upload_code_push_package.sh failed with exit code $EXIT_CODE"echo "$UPLOAD_RESPONSE"exit $EXIT_CODEfi# 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 indicatorsif echo "$UPLOAD_RESPONSE" | grep -q "ERR_INTERNAL"; thenERROR_MESSAGE=$(echo "$FINAL_RESPONSE" | jq -r '.message' || echo "Unknown error")echo "❌ Server returned internal error: $ERROR_MESSAGE"exit 1fi# Now safely parse 'status' and 'status_reason' from the final response linePACKAGE_STATUS=$(echo "$FINAL_RESPONSE" | jq -r '.status' || echo "null")STATUS_REASON=$(echo "$FINAL_RESPONSE" | jq -r '.status_reason' || echo "")if [ "$PACKAGE_STATUS" = "processed_valid" ]; thenecho "✅ Package uploaded and processed successfully."elseecho "⚠️ Package upload unexpected status: $PACKAGE_STATUS - Reason: $STATUS_REASON"exit 1ficd ..
Automating the CodePush update releaseClick to copy link
After creating a Workflow to generate and upload your CodePush updates, you have two ways of running it:
- Run the Workflow manually.
- 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:
Release a CodePush update when a pull request to a specific branch gets 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 runs when a pull request to the branch receives therelease-updatelabel.
-
Create the trigger: set up a pull request trigger with two conditions,
target_branchandlabel:- Set
target_branchtoupdates. - Set
labeltorelease-update.
GUI configYou 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.gitproject_type: react-nativeworkflows:codepush_update_deploy:status_report_name: 'Executing <target_id> for <project_title>'description: |Uploads Update Bundle to Bitrisesteps:[...]triggers:pull_request:- target_branch: updateslabel: release-update - Set
-
When you want to release CodePush updates, open a pull request to the
updatesbranch. -
After the pull request has been reviewed and approved, add the
release-updatelabel to it.
The build runs on the code of the pull request, not on the merged branch. Bitrise releases the update before you merge the pull request.