Skip to content

Instantly share code, notes, and snippets.

@JoeM-RP
Last active February 8, 2024 18:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoeM-RP/fda1a3519c8cb11906288e1b3efc4e2f to your computer and use it in GitHub Desktop.
Save JoeM-RP/fda1a3519c8cb11906288e1b3efc4e2f to your computer and use it in GitHub Desktop.
# This is an expo pipeline configuration
# https://docs.expo.dev/build/building-on-ci/
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
#
# You can specify a cache to speed up build times
# https://support.atlassian.com/bitbucket-cloud/docs/cache-dependencies/
#
# Local build reference:
# https://docs.expo.dev/build-reference/local-builds/
#
# Limitations
# Some of the options available for cloud builds are not available locally. Limitations you should be aware of:
# You can only build for a specific platform (option all is disabled).
# Customizing versions of software is not supported, fields node, yarn, fastlane, cocoapods, ndk, image in eas.json are ignored.
# Caching is not supported.
# EAS Secrets are not supported (set them in your environment locally instead).
# You are responsible for making sure that the environment have all necessary tools installed:
# - Node.js/yarn/npm
# - fastlane (iOS only)
# - CocoaPods (iOS only)
# - Android SDK and NDK
image: node:16.16.0
clone:
depth: full # SonarCloud scanner needs the full history to assign issues properly
definitions:
caches:
node: ./node_modules
yarn: /usr/local/share/.cache/yarn
sonar: ~/.sonar/cache
# https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/
steps:
- parallel:
- step: &lint
name: Lint app
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- yarn
- yarn lint --quiet
- step: &test
name: Test and analyze on Jest
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- mv .test.env .env
- yarn
- yarn test:ci
artifacts:
- coverage/**
- step: &doctor
name: Expo doctor
script:
- yarn
- npx expo-doctor
- step: &test-sonarcloud
name: Test and analyze on SonarCloud
caches:
- node
- sonar
script:
- pipe: sonarsource/sonarcloud-scan:2.0.0
variables:
SONAR_TOKEN: $SONAR_TOKEN
EXTRA_ARGS: '-Dsonar.javascript.lcov.reportPaths="$BITBUCKET_CLONE_DIR/coverage/lcov.info"'
- pipe: sonarsource/sonarcloud-quality-gate:0.1.6
variables:
SONAR_TOKEN: $SONAR_TOKEN
- step: &build
name: Build app
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- yarn
- npx eas-cli build --platform all --non-interactive --profile preview
- step: &distribute
name: Validate & Distribute App
deployment: qa
caches:
- node
- yarn
script:
- apt-get update
- apt-get -y install jq
- mv .npmrc_config .npmrc
- yarn
# Set the build profile based on deployment config, which allows us to target the right buildProfile from Expo
- PROFILE=preview
- if [ ! $BITBUCKET_DEPLOYMENT_ENVIRONMENT = uat ]; then PROFILE=production; fi
- echo "Validating build output and distributing to $PROFILE channel"
# iOS
- IOS=$(npx eas-cli build:list --json --limit=1 --platform=ios --buildProfile $PROFILE --non-interactive --status finished)
- IBU=$(echo $IOS | jq -r '.[0].artifacts.buildUrl')
- IBV=$(echo $IOS | jq -r '.[0].appBuildVersion')
- IAV=$(echo $IOS | jq -r '.[0].appVersion')
- IRV=$(echo $IOS | jq -r '.[0].runtimeVersion')
# Check if the app version and runtime version match
- if [ ! $IAV = $IRV ]; then echo "iOS runtime vesion $IRV does not match app version $IAV. This probably means something has changed in-code that the pipeline does not currenlty handle"; exit 1; fi
# Android
- AND=$(npx eas-cli build:list --json --limit=1 --platform=android --buildProfile $PROFILE --non-interactive --status finished)
- ABU=$(echo $AND | jq -r '.[0].artifacts.buildUrl')
- ABV=$(echo $AND | jq -r '.[0].appBuildVersion')
- AAV=$(echo $AND | jq -r '.[0].appVersion')
- ARV=$(echo $AND | jq -r '.[0].runtimeVersion')
# Check if the app version and runtime version match
- if [ ! $AAV = $ARV ]; then echo "Android runtime vesion $ARV does not match app version $AAV. This probably means something has changed in-code that the pipeline does not currenlty handle"; exit 1; fi
# Check that iOS and Android build versions match
- if [ ! $IBV = $ABV ]; then echo "iOS Build Version $IBV does not match Android Build Version $ABV. While this has no technical implications, we generally try to keep these numbers unified for tracking purposes. Use `npx eas-cli build:version:set` to sync build versions, then re-run the pipeline"; exit 1; fi
- echo "iOS Build Version $IBV matches Android Build Version $ABV. Pass"
# Check that iOS and Android app versions match
- if [ ! $IAV = $AAV ]; then echo "iOS App Version $IAV does not match Android App Version $AAV. Use \`npx eas-cli build:version:set\` to sync app versions, then re-run the pipeline"; exit 1; fi
- echo "iOS App Version $IAV matches Android App Version $AAV. Pass"
- echo "Disribute app version $IAV build $IBV".
# iOS AppLive Release
- curl -u "$BROWSERSTACK_TOKEN" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "data={\"url\":\"$IBU\"}"
- echo "ios download url $IBU"
# Android AppLive Release
- curl -u "$BROWSERSTACK_TOKEN" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "data={\"url\":\"$ABU\"}"
- echo "android download url $ABU"
- step: &prepare
name: Prepare app for release
caches:
- node
- yarn
script:
- FNF=" not found. Ensure the file exists or is no longer needed and update this pipeline accordingly"
- mv .npmrc_config .npmrc
- yarn
- apt-get install -y sed # needed?
# When the build is triggered from a branch, the target name is the branch name. If triggered from a tag, we must
# use the tag name as the target name. If neither are present, we use the default target name.
#
# See: https://support.atlassian.com/bitbucket-cloud/docs/pipeline-start-conditions/#Tags
- TGT=${BITBUCKET_BRANCH:-$BITBUCKET_TAG}
- echo "Target name is $TGT"
# Set app version i.e 2024.0.1 based on the branch name
- REV=$(echo "$TGT" | grep -Eo '[0-9]{4}.[0-9]{1,2}.[0-9]{1,2}')
- echo "Set App Version to $REV"
# Confirm a file exists then replace a string in the file. If the file does not exist, exit with an error.
# The edits occurs in-place and are not committed back to the repository.
- if [ ! -e "app.json" ]; then echo "app.json $FNF"; exit 1; fi
- test -e app.json && sed -i "s/0000.0.0/$REV/g" app.json
- if [ ! -f ios/MyExpoApp/Info.plist ]; then echo "ios/MyExpoApp/Info.plist $FNF"; exit 1; fi
- sed -i "s/0000.0.0/$REV/g" ios/MyExpoApp/Info.plist
- if [ ! -f ios/MyExpoApp/Supporting/Expo.plist ]; then echo "ios/MyExpoApp/Supporting/Expo.plist $FNF"; exit 1; fi
- sed -i "s/0000.0.0/$REV/g" ios/MyExpoApp/Supporting/Expo.plist
- if [ ! -f android/app/build.gradle ]; then echo "android/app/build.gradle $FNF"; exit 1; fi
- sed -i "s/0000.0.0/$REV/g" android/app/build.gradle
- if [ ! -f android/app/src/main/res/values/strings.xml ]; then echo "android/app/src/main/res/values/strings.xml $FNF"; exit 1; fi
- sed -i "s/0000.0.0/$REV/g" android/app/src/main/res/values/strings.xml
# Remove dev bundle id (Deprecated)
# - if [ ! -f android/app/build.gradle ]; then echo "android/app/build.gradle $FNF"; exit 1; fi
# - sed -i ''s/applicationIdSuffix ".dev" \/\/ Remove this for store releases.//g" android/app/build.gradle
# Set Adobe App ID
- echo "Set Adobe App ID to $ADOBE_APP_ID_PROD"
# N.B - Adobe App ID contains a / so we supply sed with an alternate delimiter
- if [ ! -f android/app/src/main/java/com/MyExpoApp/mobile/MainApplication.java ]; then echo "android/app/src/main/java/com/MyExpoApp/mobile/MainApplication.java $FNF"; exit 1; fi
- sed -i "s~ADOBE_APP_ID_PLACEHOLDER~$ADOBE_APP_ID_PROD~g" android/app/src/main/java/com/MyExpoApp/mobile/MainApplication.java
- if [ ! -f ios/AdobeBridge.m ]; then echo "ios/AdobeBridge.m $FNF"; exit 1; fi
- sed -i "s~ADOBE_APP_ID_PLACEHOLDER~$ADOBE_APP_ID_PROD~g" ios/AdobeBridge.m
# Build the app
- echo "Building the app for all platforms:"
- npx eas-cli build --platform all --non-interactive
- step: &pingback
name: Pingback
script:
- apt-get update
- apt-get -y install jq
- mv .npmrc_config .npmrc
- yarn
# Get the id for current PR
- PRID=$BITBUCKET_PR_ID
- if [ ! $PRID ]; then echo "No PR ID found. Exiting"; exit 0; fi # exit without error
# Set the endpoint URL
# https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/
- EPT="https://api.bitbucket.org/2.0/repositories/$BITBUCKET_WORKSPACE/$BITBUCKET_REPO_SLUG/pullrequests/$PRID/"
# Set the build profile based on deployment config, which allows us to target the right buildProfile from Expo
- PROFILE=preview
- if [ ! $BITBUCKET_DEPLOYMENT_ENVIRONMENT = uat ]; then PROFILE=production; fi
- echo "Searching for latest builds on $PROFILE channel"
# Fetch the latest completed build versions for iOS and Android. N.B - it is possible that a new, unrelated build may have
# finished since this pipeline was triggered, but before the pingback step & thus return the wrong download URL to append.
# This is a limitation of the current implementation
# iOS
- IOS=$(npx eas-cli build:list --json --limit=1 --platform=ios --buildProfile $PROFILE --non-interactive --status finished)
- IBU=$(echo $IOS | jq -r '.[0].artifacts.buildUrl')
- IBV=$(echo $IOS | jq -r '.[0].appBuildVersion')
# Android
- AND=$(npx eas-cli build:list --json --limit=1 --platform=android --buildProfile $PROFILE --non-interactive --status finished)
- ABU=$(echo $AND | jq -r '.[0].artifacts.buildUrl')
- ABV=$(echo $AND | jq -r '.[0].appBuildVersion')
# Append a warning to the PR if the build versions do not match. Technically, this should never happen since we check during the "distribute" phase
# but I've added this check here also in the event the "distribute" phase is skipped for some reason or if the build:list somehow returns different instances
- BV_WARN=""
- if [ ! $IBV = $ABV ]; then BV_WARN="> **WARN:** iOS Build Version $IBV does not match Android Build Version $ABV. While this has no technical implications, we generally try to keep these numbers unified for tracking purposes. Use \`npx eas-cli build:version:set\` to sync build versions, then re-run the pipeline."; fi
# Get PR content. Here, jq -r was also spitting out garbage for some reason, so we need to do some fancy string manipulation later
- OG_CONTENT=$(curl --location $EPT --header "Authorization:Bearer $PINGBACK_TOKEN" --header "Accept:application/json" --header "Content-Type:application/json" | jq '.summary.raw')
- echo $OG_CONTENT
# Fancy string manipulation. Remove pingback content, if it exists, from original PR description and any extra quotes which will befuddle cURL later
- DEV_CONTENT=$(echo $OG_CONTENT | sed 's/§.*//')
- DEV_CONTENT=$(echo $DEV_CONTENT | tr -d '"')
# Default content to add to PR description
- APPEND_CONTENT="§ **my-expo-app-mobile-pipeline:** ($(date '+%Y-%m-%d %r %Z')) \n\n >🍏 ($IBV): [$IBU]($IBU) \n\n >🤖 ($ABV): [$ABU]($ABU)"
# Concat the PR description, default pingback content, and optional warning (if applicable)
- NEW_CONTENT="$DEV_CONTENT\n\n\n\n$APPEND_CONTENT\n\n$BV_WARN"
- echo "$NEW_CONTENT" # check this for extra quotes here if the PUT fails later
# PUT request to update the PR description with the new content
- curl --request PUT --url $EPT --header "Authorization:Bearer $PINGBACK_TOKEN" --header "Accept:application/json" --header "Content-Type:application/json" --data "{\"description\":\"$NEW_CONTENT\"}"
pipelines:
branches:
'{main}':
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
- step: *build
pull-requests:
'*chore/*':
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
'release/*':
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
- step: *prepare
- step:
<<: *distribute
deployment: uat # for tagging/tracking purposes. These builds cannot be installed via BrowserStack, etc
- step: *pingback
- step:
name: Submit to App Stores
trigger: manual
deployment: production
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- yarn
- npx eas-cli submit --platform all --latest --non-interactive
'**': # triggers if no other specific pipeline was triggered
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
- step: *build
- step: *distribute
- step: *pingback
custom: # Pipelines that can only be triggered manually
updates-preview:
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
- step:
name: Update Patch - Preview
deployment: qa
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- yarn
- echo "Decrypt .env file from Bitbucket Secrets"
- (umask 077 ; echo $ENV | base64 --decode > $BITBUCKET_CLONE_DIR/.env)
- if [ ! -e "$BITBUCKET_CLONE_DIR/.env" ]; then echo "Missing env file"; exit 1; fi
- echo "Creating an update patch for channel PREVIEW"
- COMMIT_MESSAGE=`git log --format=%B -n 1 $BITBUCKET_COMMIT`
- echo $COMMIT_MESSAGE
- npx eas-cli update --channel preview --message "$COMMIT_MESSAGE" --non-interactive
updates-prod:
- parallel:
- step: *lint
- step: *test
- step: *doctor
- step: *test-sonarcloud
- step:
name: Update Patch - Prod
deployment: production
caches:
- node
- yarn
script:
- mv .npmrc_config .npmrc
- yarn
- echo "Decrypt .env file from Bitbucket Secrets"
- (umask 077 ; echo $ENV | base64 --decode > $BITBUCKET_CLONE_DIR/.env)
- if [ ! -e "$BITBUCKET_CLONE_DIR/.env" ]; then echo "Missing env file"; exit 1; fi
- echo "Creating an update patch for channel PRODUCTION"
- COMMIT_MESSAGE=`git log --format=%B -n 1 $BITBUCKET_COMMIT`
- echo $COMMIT_MESSAGE
- npx eas-cli update --channel production --message "$COMMIT_MESSAGE" --non-interactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment