Merge pull request #81971 from situchan/revert-81196-jsenyitko-hide-e… #236
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |