Skip to content

Merge pull request #81971 from situchan/revert-81196-jsenyitko-hide-e… #236

Merge pull request #81971 from situchan/revert-81196-jsenyitko-hide-e…

Merge pull request #81971 from situchan/revert-81196-jsenyitko-hide-e… #236

name: Build and deploy apps for testing on push
on:
push:
branches: [main]
paths-ignore: ['docs/**', 'contributingGuides/**', 'help/**', '.github/**', 'scripts/**', 'tests/**', 'jest/**', '.claude/**']
concurrency:
group: 'testBuildOnPush'
cancel-in-progress: false
jobs:
prep:
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
APP_REF: ${{ github.sha }}
APP_PR_NUMBER: ${{ steps.getMergedPullRequest.outputs.PR_NUMBER }}
PR_URL: ${{ steps.getMergedPullRequest.outputs.PR_URL }}
BUILD_WEB: ${{ steps.detectOSBotifyPush.outputs.BUILD_WEB || 'true' }}
BUILD_MOBILE: ${{ steps.detectOSBotifyPush.outputs.BUILD_MOBILE || 'true' }}
POST_COMMENTS: ${{ steps.detectOSBotifyPush.outputs.POST_COMMENTS || steps.getMergedPullRequest.outputs.POST_COMMENTS || 'true' }}
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- 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: Detect OSBotify automated push
id: detectOSBotifyPush
if: ${{ github.actor == 'OSBotify' }}
# v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
github-token: ${{ github.token }}
script: |
const commitMessage = context.payload?.head_commit?.message || '';
if (commitMessage.startsWith('Update Mobile-Expensify submodule')) {
core.notice(`${context.actor} automated push with Mobile-Expensify submodule update detected. Skipping web build and post comments. No PR associated with this commit.`);
core.setOutput('BUILD_WEB', 'false');
core.setOutput('POST_COMMENTS', 'false');
} else {
core.warning(`OSBotify automated push detected but without Mobile-Expensify submodule update commit phrase. Skipping rest of the workflow. No PR associated with this commit.`);
core.setOutput('BUILD_MOBILE', 'false');
core.setOutput('BUILD_WEB', 'false');
core.setOutput('POST_COMMENTS', 'false');
}
- name: Get merged pull request
id: getMergedPullRequest
if: ${{ github.actor != 'OSBotify' }}
# v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
github-token: ${{ github.token }}
script: |
const prData = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
});
const prNumber = prData?.data?.find(p => p.state === 'closed' && p.merged_at)?.number?.toString();
if (prNumber) {
const prUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${prNumber}`;
core.notice(`Found merged pull request: [#${prNumber}](${prUrl})`);
core.setOutput('PR_URL', prUrl);
core.setOutput('PR_NUMBER', prNumber);
} else {
core.setOutput('POST_COMMENTS', 'false');
core.setFailed(`Commit pushed by non-OSBotify actor. No merged pull request found for commit ${context.sha}`);
}
postGitHubCommentBuildStarted:
name: Post build started comment
runs-on: blacksmith-4vcpu-ubuntu-2404
if: ${{ needs.prep.outputs.POST_COMMENTS == 'true' }}
needs: [prep]
steps:
- name: Add build start comment to Expensify/App PR
if: ${{ needs.prep.outputs.APP_PR_NUMBER != '' }}
# v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
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: ${{ needs.prep.outputs.APP_PR_NUMBER }},
body: `🚧 @${{ github.actor }} has triggered a test Expensify/App build. You can view the [workflow run here](${workflowURL}).`
});
web:
name: Build and deploy Web
if: ${{ needs.prep.outputs.BUILD_WEB == 'true' && needs.prep.outputs.APP_PR_NUMBER }}
needs: [prep]
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER || '' }}
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
ref: ${{ needs.prep.outputs.APP_REF }}
- name: Create .env.adhoc file based on staging
run: |
cp .env.staging .env.adhoc
sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
- name: Inject CI data into JS bundle
run: ./.github/scripts/inject-ci-data.sh PULL_REQUEST_NUMBER="$PULL_REQUEST_NUMBER"
- name: Setup Node
uses: ./.github/actions/composite/setupNode
- name: Configure AWS Credentials
# v4
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
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
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- 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"
androidHybrid:
name: Build Android HybridApp
if: ${{ needs.prep.outputs.BUILD_MOBILE == 'true' }}
needs: [prep]
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER || '' }}
outputs:
ROCK_ANDROID_ADHOC_INDEX_URL: ${{ steps.set-artifact-url.outputs.ARTIFACT_URL }}
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
submodules: true
ref: ${{ needs.prep.outputs.APP_REF }}
token: ${{ secrets.OS_BOTIFY_TOKEN }}
- name: Configure MapBox SDK
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
- name: Setup Node
id: setup-node
uses: ./.github/actions/composite/setupNode
with:
IS_HYBRID_BUILD: 'true'
- name: Run grunt build
run: |
cd Mobile-Expensify
npm run grunt:build:shared
- name: Create .env.adhoc file based on staging
run: |
cp .env.staging .env.adhoc
sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
- name: Inject CI data into JS bundle
run: ./.github/scripts/inject-ci-data.sh PULL_REQUEST_NUMBER="$PULL_REQUEST_NUMBER"
- name: Setup 1Password CLI and certificates
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
with:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
- name: Load files from 1Password
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
op read "op://${{ vars.OP_VAULT }}/upload-key.keystore/upload-key.keystore" --force --out-file ./upload-key.keystore
op read "op://${{ vars.OP_VAULT }}/android-fastlane-json-key.json/android-fastlane-json-key.json" --force --out-file ./android-fastlane-json-key.json
# Copy the keystore to the Android directory for Fullstory
cp ./upload-key.keystore Mobile-Expensify/Android
- name: Load Android upload keystore credentials from 1Password
id: load-credentials
# v2
uses: 1password/load-secrets-action@581a835fb51b8e7ec56b71cf2ffddd7e68bb25e0
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
ANDROID_UPLOAD_KEYSTORE_PASSWORD: op://${{ vars.OP_VAULT }}/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_PASSWORD
ANDROID_UPLOAD_KEYSTORE_ALIAS: op://${{ vars.OP_VAULT }}/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_ALIAS
ANDROID_UPLOAD_KEY_PASSWORD: op://${{ vars.OP_VAULT }}/Repository-Secrets/ANDROID_UPLOAD_KEY_PASSWORD
- name: Configure AWS Credentials
# v4
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
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: Rock Remote Build - Android
id: rock-remote-build-android
uses: callstackincubator/android@0bbc1b7c2e1a8be1ecb4d6c744c211869823fd65
env:
GITHUB_TOKEN: ${{ github.token }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
IS_HYBRID_APP: true
with:
variant: 'Adhoc'
sign: true
re-sign: true
ad-hoc: true
keystore-file: './upload-key.keystore'
keystore-store-file: 'upload-key.keystore'
keystore-store-password: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }}
keystore-key-alias: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }}
keystore-key-password: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }}
# Specify the path (relative to the Android source directory) where the keystore should be placed.
keystore-path: '../tools/buildtools/upload-key.keystore'
comment-bot: false
rock-build-extra-params: '--extra-params -PreactNativeArchitectures=arm64-v8a,x86_64'
- name: Set artifact URL output
id: set-artifact-url
run: echo "ARTIFACT_URL=$ARTIFACT_URL" >> "$GITHUB_OUTPUT"
iosHybrid:
name: Build and deploy iOS for testing
if: ${{ needs.prep.outputs.BUILD_MOBILE == 'true' }}
needs: [prep]
env:
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
PULL_REQUEST_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER || '' }}
runs-on: macos-15-xlarge
outputs:
ROCK_IOS_ADHOC_INDEX_URL: ${{ steps.set-artifact-url.outputs.ARTIFACT_URL }}
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
submodules: true
ref: ${{ needs.prep.outputs.APP_REF }}
token: ${{ secrets.OS_BOTIFY_TOKEN }}
- name: Configure MapBox SDK
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
- name: Setup Node
id: setup-node
uses: ./.github/actions/composite/setupNode
with:
IS_HYBRID_BUILD: 'true'
- name: Create .env.adhoc file based on staging
run: |
cp .env.staging .env.adhoc
sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc
- name: Inject CI data into JS bundle
run: ./.github/scripts/inject-ci-data.sh PULL_REQUEST_NUMBER="$PULL_REQUEST_NUMBER"
- name: Setup 1Password CLI and certificates
uses: Expensify/GitHub-Actions/setup-certificate-1p@main
with:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
SHOULD_LOAD_SSL_CERTIFICATES: 'false'
- name: Load files from 1Password
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
op read "op://${{ vars.OP_VAULT }}/OldApp_AdHoc/OldApp_AdHoc.mobileprovision" --force --out-file ./OldApp_AdHoc.mobileprovision
op read "op://${{ vars.OP_VAULT }}/OldApp_AdHoc_Share_Extension/OldApp_AdHoc_Share_Extension.mobileprovision" --force --out-file ./OldApp_AdHoc_Share_Extension.mobileprovision
op read "op://${{ vars.OP_VAULT }}/OldApp_AdHoc_Notification_Service/OldApp_AdHoc_Notification_Service.mobileprovision" --force --out-file ./OldApp_AdHoc_Notification_Service.mobileprovision
op read "op://${{ vars.OP_VAULT }}/New Expensify Distribution Certificate/Certificates.p12" --force --out-file ./Certificates.p12
- name: Create ExportOptions.plist
run: |
cat > Mobile-Expensify/iOS/ExportOptions.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>ad-hoc</string>
<key>provisioningProfiles</key>
<dict>
<key>com.expensify.expensifylite.adhoc</key>
<string>(OldApp) AdHoc</string>
<key>com.expensify.expensifylite.adhoc.SmartScanExtension</key>
<string>(OldApp) AdHoc: Share Extension</string>
<key>com.expensify.expensifylite.adhoc.NotificationServiceExtension</key>
<string>(OldApp) AdHoc: Notification Service</string>
</dict>
</dict>
</plist>
EOF
- name: Configure AWS Credentials
# v4
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722
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: Rock Remote Build - iOS
id: rock-remote-build-ios
uses: callstackincubator/ios@8dcef6cc275e0cf3299f5a97cde5ebd635c887d7
env:
GITHUB_TOKEN: ${{ github.token }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
IS_HYBRID_APP: true
with:
destination: device
re-sign: true
ad-hoc: true
scheme: 'Expensify AdHoc'
configuration: 'AdHoc'
certificate-file: './Certificates.p12'
provisioning-profiles: |
[
{
"name": "(OldApp) AdHoc",
"file": "./OldApp_AdHoc.mobileprovision"
},
{
"name": "(OldApp) AdHoc: Share Extension",
"file": "./OldApp_AdHoc_Share_Extension.mobileprovision"
},
{
"name": "(OldApp) AdHoc: Notification Service",
"file": "./OldApp_AdHoc_Notification_Service.mobileprovision"
}
]
comment-bot: false
- name: Set artifact URL output
id: set-artifact-url
run: echo "ARTIFACT_URL=$ARTIFACT_URL" >> "$GITHUB_OUTPUT"
postGithubComment:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: ${{ always() && needs.prep.outputs.POST_COMMENTS == 'true' }}
name: Post a GitHub comment with app download links for testing
needs: [prep, web, androidHybrid, iosHybrid]
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
ref: ${{ needs.prep.outputs.APP_REF }}
- name: Publish links to apps for download on Expensify/App PR
if: ${{ needs.prep.outputs.APP_PR_NUMBER }}
uses: ./.github/actions/javascript/postTestBuildComment
with:
REPO: App
APP_PR_NUMBER: ${{ needs.prep.outputs.APP_PR_NUMBER }}
GITHUB_TOKEN: ${{ github.token }}
ANDROID: ${{ needs.androidHybrid.result }}
IOS: ${{ needs.iosHybrid.result }}
WEB: ${{ needs.web.result }}
ANDROID_LINK: ${{ needs.androidHybrid.outputs.ROCK_ANDROID_ADHOC_INDEX_URL || ''}}
IOS_LINK: ${{ needs.iosHybrid.outputs.ROCK_IOS_ADHOC_INDEX_URL || ''}}
WEB_LINK: https://${{ needs.prep.outputs.APP_PR_NUMBER }}.pr-testing.expensify.com
buildSummary:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: ${{ always() && (needs.prep.outputs.APP_PR_NUMBER != '' || needs.androidHybrid.outputs.ROCK_ANDROID_ADHOC_INDEX_URL != '' || needs.iosHybrid.outputs.ROCK_IOS_ADHOC_INDEX_URL != '') }}
name: Build Summary
needs: [prep, web, androidHybrid, iosHybrid]
steps:
- name: Checkout
# v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
ref: ${{ needs.prep.outputs.APP_REF }}
- name: Get Mobile-Expensify submodule SHA
id: getSubmoduleSHA
run: |
if [ -d "Mobile-Expensify" ]; then
SUBMODULE_SHA=$(git ls-tree HEAD Mobile-Expensify | awk '{print $3}')
echo "SHA=$SUBMODULE_SHA" >> "$GITHUB_OUTPUT"
fi
- name: Create build summary
# v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
github-token: ${{ github.token }}
script: |
const prNumber = '${{ needs.prep.outputs.APP_PR_NUMBER }}';
const webLink = prNumber && '${{ needs.web.result }}' === 'success' ? `https://${prNumber}.pr-testing.expensify.com` : '';
const androidLink = '${{ needs.androidHybrid.outputs.ROCK_ANDROID_ADHOC_INDEX_URL }}' || '';
const iosLink = '${{ needs.iosHybrid.outputs.ROCK_IOS_ADHOC_INDEX_URL }}' || '';
const submoduleSHA = '${{ steps.getSubmoduleSHA.outputs.SHA }}' || '';
const webStatus = webLink ? '✅ Success' : '${{ needs.web.result }}' === 'failure' ? '❌ Failed' : '⏭️ Skipped';
const androidStatus = androidLink ? '✅ Success' : '${{ needs.androidHybrid.result }}' === 'failure' ? '❌ Failed' : '⏭️ Skipped';
const iosStatus = iosLink ? '✅ Success' : '${{ needs.iosHybrid.result }}' === 'failure' ? '❌ Failed' : '⏭️ Skipped';
const summary = core.summary
.addTable([
[{data: 'Platform', header: true}, {data: 'Status', header: true}, {data: 'Download', header: true}],
[
'Web',
webStatus,
webLink ? `<a href="${webLink}">${webLink}</a>` : '-'
],
[
'Android',
androidStatus,
androidLink ? `<a href="${androidLink}">${androidLink}</a>` : '-'
],
[
'iOS',
iosStatus,
iosLink ? `<a href="${iosLink}">${iosLink}</a>` : '-'
],
]);
if (submoduleSHA) {
const submoduleUrl = `https://github.com/Expensify/Mobile-Expensify/commit/${submoduleSHA}`;
summary.addRaw(`\n**Mobile-Expensify Submodule SHA:** [${submoduleSHA}](${submoduleUrl})`);
}
const prUrl = '${{ needs.prep.outputs.PR_URL }}';
if (prUrl) {
summary.addRaw(`\n**PR Link:** ${prUrl}`);
} else {
summary.addRaw(`\n**PR Link:** No PR associated with this commit.`);
}
summary.write();