CodePush updates with Bitrise CI
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 BitriseClick to copy link
-
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 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 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.gitproject_type: iosworkflows: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 Versioninputs:- content: |-#!/bin/bashset -e# Extract version from app.json using jq (JSON parser)VERSION=$(jq -r '.version' package.json)# Verify version extractionif [ -z "$VERSION" ]; thenecho "Error: Could not extract version from package.json"exit 1fiecho "Extracted version from package.json: $VERSION"# Set the environment variable using envmanenvman 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 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 -xnpx 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 the CodePush Server with
upload_code_push_package.sh:- script@1:title: Upload iOS Update Bundle to Codepush Serverinputs:- 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_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_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 -xnpx 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 the CodePush Server:
- script@1:title: Upload Android Update Bundle to Codepush Serverinputs:- 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_VERSION /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: iosworkflows:codepush_update_deploy:description: |Uploads Update Bundle to Bitrise CodePush Serverstatus_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 Versioninputs:- content: |-#!/bin/bashset -e# Simpler version using awkAPP_VERSION=$(awk -F'"' '/version:/ {print $2}' app.config.js)# Check if the version was successfully extractedif [ -z "$APP_VERSION" ]; thenecho "Error: Failed to extract version from app.config.js"exit 1fiecho "Extracted version: $APP_VERSION"# Set the environment variable using envmanenvman 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 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# write your script herenpx 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 the CodePush Server with
upload_code_push_package.sh:- script@1:title: Upload iOS Update Bundle to Codepush Serverinputs:- 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_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_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# write your script herenpx 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 the CodePush Server:
- script@1:title: Upload Android Update Bundle to Codepush Serverinputs:- 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_VERSION /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 is opened to a specific branch
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 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 Bitrise CodePush Serversteps:[...]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 PR has been reviewed and approved, add the
release-updatelabel to it.