Skip to content

Build and deploy apps for testing #2707

Build and deploy apps for testing

Build and deploy apps for testing #2707

Workflow file for this run

name: Build and deploy apps for testing
on:
workflow_dispatch:
inputs:
PULL_REQUEST_NUMBER:
description: Pull Request number for correct placement of apps
required: true
pull_request_target:
types: [opened, synchronize, labeled]
branches: ['*ci-test/**']
env:
PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }}
jobs:
prep:
runs-on: ubuntu-latest
outputs:
REF: ${{ github.event.pull_request.head.sha || steps.getHeadRef.outputs.REF }}
READY_TO_BUILD: ${{ steps.readyToBuild.outputs.READY_TO_BUILD }}
steps:
- name: Checkout
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/checkout@v4
- name: Validate that user is an Expensify employee
uses: ./.github/actions/composite/validateActor
with:
REQUIRE_APP_DEPLOYER: false
OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }}
- name: Check if PR has Ready to Build label
id: readyToBuild
run: |
LABELS=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name')
if echo "$LABELS" | grep -q 'Ready To Build'; then
echo "::notice::✅ PR ${{ env.PULL_REQUEST_NUMBER }} has 'Ready to Build' label"
echo "READY_TO_BUILD=true" >> "$GITHUB_OUTPUT"
else
echo "::error::❌ PR ${{ env.PULL_REQUEST_NUMBER }} does not have 'Ready to Build' label"
echo "READY_TO_BUILD=false" >> "$GITHUB_OUTPUT"
exit 1
fi
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Check if pull request number is correct
if: ${{ github.event_name == 'workflow_dispatch' }}
id: getHeadRef
run: |
set -e
echo "REF=$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: ${{ github.token }}
postGitHubCommentBuildStarted:
runs-on: ubuntu-latest
needs: [prep]
steps:
- name: Add build start comment
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const workflowURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: process.env.PULL_REQUEST_NUMBER,
body: `🚧 @${{ github.actor }} has triggered a test build. You can view the [workflow run here](${workflowURL}).`
});
buildAndroid:
name: Build Android app for testing
uses: ./.github/workflows/buildAndroid.yml
needs: [prep]
secrets: inherit
with:
type: adhoc
ref: ${{ needs.prep.outputs.REF }}
pull_request_number: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }}
uploadAndroid:
name: Upload Android app to S3
needs: [buildAndroid]
runs-on: ubuntu-latest
outputs:
S3_APK_PATH: ${{ steps.exportS3Path.outputs.S3_APK_PATH }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/[email protected]
with:
bundler-cache: true
- name: Download Android build artifacts
uses: actions/download-artifact@v4
with:
path: /tmp/artifacts
pattern: android-*-artifact
merge-multiple: true
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Upload AdHoc build to S3
run: bundle exec fastlane android upload_s3
env:
apkPath: /tmp/artifacts/${{ needs.buildAndroid.outputs.APK_FILE_NAME }}
S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_BUCKET: ad-hoc-expensify-cash
S3_REGION: us-east-1
- name: Export S3 paths
id: exportS3Path
run: |
# $s3APKPath is set from within the Fastfile, android upload_s3 lane
echo "S3_APK_PATH=$s3APKPath" >> "$GITHUB_OUTPUT"
iOS:
name: Build and deploy iOS for testing
needs: [prep]
env:
DEVELOPER_DIR: /Applications/Xcode_16.2.0.app/Contents/Developer
runs-on: macos-15-xlarge
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.prep.outputs.REF }}
- name: Configure MapBox SDK
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
- name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it
run: |
cp .env.staging .env.adhoc
sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc
- name: Setup Node
id: setup-node
uses: ./.github/actions/composite/setupNode
- name: Setup XCode
run: sudo xcode-select -switch /Applications/Xcode_16.2.0.app
- name: Setup Ruby
uses: ruby/[email protected]
with:
bundler-cache: true
- name: Cache Pod dependencies
uses: actions/cache@v4
id: pods-cache
with:
path: ios/Pods
key: ${{ runner.os }}-pods-cache-${{ hashFiles('ios/Podfile.lock', 'firebase.json') }}
- name: Compare Podfile.lock and Manifest.lock
id: compare-podfile-and-manifest
run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('ios/Podfile.lock') == hashFiles('ios/Pods/Manifest.lock') }}" >> "$GITHUB_OUTPUT"
- name: Install cocoapods
uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847
if: steps.pods-cache.outputs.cache-hit != 'true' || steps.compare-podfile-and-manifest.outputs.IS_PODFILE_SAME_AS_MANIFEST != 'true' || steps.setup-node.outputs.cache-hit != 'true'
with:
timeout_minutes: 10
max_attempts: 5
command: scripts/pod-install.sh
- name: Install 1Password CLI
uses: 1password/install-cli-action@v1
- name: Load files from 1Password
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
op read "op://Mobile-Deploy-CI/NewApp_AdHoc/NewApp_AdHoc.mobileprovision" --force --out-file ./NewApp_AdHoc.mobileprovision
op read "op://Mobile-Deploy-CI/NewApp_AdHoc_Notification_Service/NewApp_AdHoc_Notification_Service.mobileprovision" --force --out-file ./NewApp_AdHoc_Notification_Service.mobileprovision
op read "op://Mobile-Deploy-CI/NewApp_AdHoc_Share_Extension.mobileprovision/NewApp_AdHoc_Share_Extension.mobileprovision" --force --out-file ./NewApp_AdHoc_Share_Extension.mobileprovision
op read "op://Mobile-Deploy-CI/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Build AdHoc app
run: bundle exec fastlane ios build_adhoc
- name: Upload AdHoc build to S3
run: bundle exec fastlane ios upload_s3
env:
S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_BUCKET: ad-hoc-expensify-cash
S3_REGION: us-east-1
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: ios
path: ./ios_paths.json
desktop:
name: Build and deploy Desktop for testing
needs: [prep]
runs-on: macos-14-large
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.prep.outputs.REF }}
- name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it
run: |
cp .env.staging .env.adhoc
sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc
- name: Setup Node
uses: ./.github/actions/composite/setupNode
- name: Decrypt Developer ID Certificate
run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg
env:
DEVELOPER_ID_SECRET_PASSPHRASE: ${{ secrets.DEVELOPER_ID_SECRET_PASSPHRASE }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Build desktop app for testing
run: npm run desktop-build-adhoc
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_STAGING }}
web:
name: Build and deploy Web
needs: [prep]
runs-on: ubuntu-latest-xl
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.prep.outputs.REF }}
- name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it
run: |
cp .env.staging .env.adhoc
sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc
- name: Setup Node
uses: ./.github/actions/composite/setupNode
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Build web for testing
run: npm run build-adhoc
- name: Deploy to S3 for internal testing
run: aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist s3://ad-hoc-expensify-cash/web/"$PULL_REQUEST_NUMBER"
postGithubComment:
runs-on: ubuntu-latest
name: Post a GitHub comment with app download links for testing
needs: [prep, uploadAndroid, iOS, desktop, web]
if: ${{ always() && needs.prep.outputs.READY_TO_BUILD == 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.prep.outputs.REF }}
- name: Download Artifact
uses: actions/download-artifact@v4
- name: Read JSONs with iOS paths
id: get_ios_path
if: ${{ needs.iOS.result == 'success' }}
run: |
content_ios="$(cat ./ios/ios_paths.json)"
content_ios="${content_ios//'%'/'%25'}"
content_ios="${content_ios//$'\n'/'%0A'}"
content_ios="${content_ios//$'\r'/'%0D'}"
ios_path=$(echo "$content_ios" | jq -r '.html_path')
echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT"
- name: Publish links to apps for download
uses: ./.github/actions/javascript/postTestBuildComment
with:
PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }}
GITHUB_TOKEN: ${{ github.token }}
ANDROID: ${{ needs.uploadAndroid.result }}
DESKTOP: ${{ needs.desktop.result }}
IOS: ${{ needs.iOS.result }}
WEB: ${{ needs.web.result }}
ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }}
DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ env.PULL_REQUEST_NUMBER }}/NewExpensify.dmg
IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }}
WEB_LINK: https://${{ env.PULL_REQUEST_NUMBER }}.pr-testing.expensify.com