Merge pull request #49235 from Expensify/cmartins-fixQueryName #3169
This file contains 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: Deploy code to staging or production | |
on: | |
push: | |
branches: [staging, production] | |
env: | |
SHOULD_DEPLOY_PRODUCTION: ${{ github.ref == 'refs/heads/production' }} | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref }} | |
cancel-in-progress: true | |
jobs: | |
validateActor: | |
runs-on: ubuntu-latest | |
timeout-minutes: 90 | |
outputs: | |
IS_DEPLOYER: ${{ fromJSON(steps.isUserDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' || github.actor == 'os-botify[bot]' }} | |
steps: | |
- name: Check if user is deployer | |
id: isUserDeployer | |
run: | | |
if gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then | |
echo "IS_DEPLOYER=true" >> "$GITHUB_OUTPUT" | |
else | |
echo "IS_DEPLOYER=false" >> "$GITHUB_OUTPUT" | |
fi | |
env: | |
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} | |
prep: | |
needs: validateActor | |
if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} | |
runs-on: ubuntu-latest | |
outputs: | |
APP_VERSION: ${{ steps.getAppVersion.outputs.VERSION }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
token: ${{ secrets.OS_BOTIFY_TOKEN }} | |
- name: Setup git for OSBotify | |
uses: ./.github/actions/composite/setupGitForOSBotifyApp | |
id: setupGitForOSBotify | |
with: | |
GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} | |
OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} | |
- name: Get app version | |
id: getAppVersion | |
run: echo "VERSION=$(jq -r .version < package.json)" >> "$GITHUB_OUTPUT" | |
- name: Create and push tag | |
if: ${{ github.ref == 'refs/heads/staging' }} | |
run: | | |
git tag ${{ steps.getAppVersion.outputs.VERSION }} | |
git push origin --tags | |
# Note: we're updating the checklist before running the deploys and assuming that it will succeed on at least one platform | |
deployChecklist: | |
name: Create or update deploy checklist | |
uses: ./.github/workflows/createDeployChecklist.yml | |
if: ${{ github.ref == 'refs/heads/staging' }} | |
needs: prep | |
secrets: inherit | |
android: | |
name: Build and deploy Android | |
needs: prep | |
runs-on: ubuntu-latest-xl | |
env: | |
RUBYOPT: '-rostruct' | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Configure MapBox SDK | |
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} | |
- name: Setup Node | |
uses: ./.github/actions/composite/setupNode | |
- name: Setup Java | |
uses: actions/setup-java@v4 | |
with: | |
distribution: 'oracle' | |
java-version: '17' | |
- name: Setup Ruby | |
uses: ruby/[email protected] | |
with: | |
bundler-cache: true | |
- name: Decrypt keystore | |
run: cd android/app && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output my-upload-key.keystore my-upload-key.keystore.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Decrypt json key | |
run: cd android/app && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output android-fastlane-json-key.json android-fastlane-json-key.json.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Get Android native version | |
id: getAndroidVersion | |
run: echo "VERSION_CODE=$(grep -o 'versionCode\s\+[0-9]\+' android/app/build.gradle | awk '{ print $2 }')" >> "$GITHUB_OUTPUT" | |
- name: Build Android app | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: bundle exec fastlane android build | |
env: | |
MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} | |
MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} | |
- name: Upload Android app to Google Play | |
run: bundle exec fastlane android ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'upload_google_play_production' || 'upload_google_play_internal' }} | |
env: | |
VERSION: ${{ steps.getAndroidVersion.outputs.VERSION_CODE }} | |
- name: Upload Android build to Browser Stack | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@./android/app/build/outputs/bundle/productionRelease/app-production-release.aab" | |
env: | |
BROWSERSTACK: ${{ secrets.BROWSERSTACK }} | |
- name: Upload Android sourcemaps artifact | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: android-sourcemaps-artifact | |
path: ./android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map | |
- name: Upload Android build artifact | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: android-build-artifact | |
path: ./android/app/build/outputs/bundle/productionRelease/app-production-release.aab | |
- name: Set current App version in Env | |
run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" | |
- name: Warn deployers if Android production deploy failed | |
if: ${{ failure() && fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#deployer', | |
attachments: [{ | |
color: "#DB4545", | |
pretext: `<!subteam^S4TJJ3PSL>`, | |
text: `💥 Android production deploy failed. Please manually submit ${{ needs.prep.outputs.APP_VERSION }} in the <https://play.google.com/console/u/0/developers/8765590895836334604/app/4973041797096886180/releases/overview|Google Play Store>. 💥`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
desktop: | |
name: Build and deploy Desktop | |
needs: prep | |
runs-on: macos-14-large | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- 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: Build desktop app | |
run: | | |
if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then | |
npm run desktop-build | |
else | |
npm run desktop-build-staging | |
fi | |
env: | |
CSC_LINK: ${{ secrets.CSC_LINK }} | |
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
APPLE_ID: ${{ secrets.APPLE_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_PRODUCTION }} | |
- name: Upload desktop sourcemaps artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'desktop-sourcemaps-artifact' || 'desktop-staging-sourcemaps-artifact' }} | |
path: ./desktop/dist/www/merged-source-map.js.map | |
- name: Upload desktop build artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'desktop-build-artifact' || 'desktop-staging-build-artifact' }} | |
path: ./desktop-build/NewExpensify.dmg | |
iOS: | |
name: Build and deploy iOS | |
needs: prep | |
env: | |
DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer | |
runs-on: macos-13-xlarge | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- 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 | |
- 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: Decrypt AppStore profile | |
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore.mobileprovision NewApp_AppStore.mobileprovision.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Decrypt AppStore Notification Service profile | |
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore_Notification_Service.mobileprovision NewApp_AppStore_Notification_Service.mobileprovision.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Decrypt certificate | |
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Decrypt App Store Connect API key | |
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output ios-fastlane-json-key.json ios-fastlane-json-key.json.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Set current App version in Env | |
run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" | |
- name: Get iOS native version | |
id: getIOSVersion | |
run: echo "IOS_VERSION=$(echo '${{ needs.prep.outputs.APP_VERSION }}' | tr '-' '.')" >> "$GITHUB_OUTPUT" | |
- name: Build iOS release app | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: bundle exec fastlane ios build | |
- name: Upload release build to TestFlight | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: bundle exec fastlane ios upload_testflight | |
env: | |
APPLE_CONTACT_EMAIL: ${{ secrets.APPLE_CONTACT_EMAIL }} | |
APPLE_CONTACT_PHONE: ${{ secrets.APPLE_CONTACT_PHONE }} | |
APPLE_DEMO_EMAIL: ${{ secrets.APPLE_DEMO_EMAIL }} | |
APPLE_DEMO_PASSWORD: ${{ secrets.APPLE_DEMO_PASSWORD }} | |
- name: Submit build for App Store review | |
if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: bundle exec fastlane ios submit_for_review | |
env: | |
VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }} | |
- name: Upload iOS build to Browser Stack | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/Users/runner/work/App/App/New Expensify.ipa" | |
env: | |
BROWSERSTACK: ${{ secrets.BROWSERSTACK }} | |
- name: Upload iOS sourcemaps artifact | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ios-sourcemaps-artifact | |
path: ./main.jsbundle.map | |
- name: Upload iOS build artifact | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ios-build-artifact | |
path: /Users/runner/work/App/App/New\ Expensify.ipa | |
- name: Warn deployers if iOS production deploy failed | |
if: ${{ failure() && fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#deployer', | |
attachments: [{ | |
color: "#DB4545", | |
pretext: `<!subteam^S4TJJ3PSL>`, | |
text: `💥 iOS production deploy failed. Please manually submit ${{ steps.getIOSVersion.outputs.IOS_VERSION }} in the <https://appstoreconnect.apple.com/apps/1530278510/appstore|App Store>. 💥`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
web: | |
name: Build and deploy Web | |
needs: prep | |
runs-on: ubuntu-latest-xl | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Setup Node | |
uses: ./.github/actions/composite/setupNode | |
- name: Setup Cloudflare CLI | |
run: pip3 install cloudflare==2.19.0 | |
- 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 | |
run: | | |
if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then | |
npm run build | |
else | |
npm run build-staging | |
fi | |
- name: Build storybook docs | |
continue-on-error: true | |
run: | | |
if [[ ${{ env.SHOULD_DEPLOY_PRODUCTION }} == 'true' ]]; then | |
npm run storybook-build | |
else | |
npm run storybook-build-staging | |
fi | |
- name: Deploy to S3 | |
run: | | |
aws s3 cp --recursive --acl public-read "$GITHUB_WORKSPACE"/dist ${{ env.S3_URL }}/ | |
aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_URL }}/.well-known/apple-app-site-association ${{ env.S3_URL }}/.well-known/apple-app-site-association | |
aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_URL }}/.well-known/apple-app-site-association ${{env.S3_URL }}/apple-app-site-association | |
env: | |
S3_URL: s3://${{ env.SHOULD_DEPLOY_PRODUCTION != 'true' && 'staging-' || '' }}expensify-cash | |
- name: Purge Cloudflare cache | |
run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["${{ env.SHOULD_DEPLOY_PRODUCTION != 'true' && 'staging.' || '' }}new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache | |
env: | |
CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} | |
- name: Set current App version in Env | |
run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" | |
- name: Verify staging deploy | |
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: | | |
sleep 5 | |
DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://staging.new.expensify.com/version.json | jq -r '.version')" | |
if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then | |
echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..." | |
exit 1 | |
fi | |
- name: Verify production deploy | |
if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
run: | | |
sleep 5 | |
DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://new.expensify.com/version.json | jq -r '.version')" | |
if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then | |
echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..." | |
exit 1 | |
fi | |
- name: Upload web sourcemaps artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'web' || 'web-staging' }}-sourcemaps-artifact | |
path: ./dist/merged-source-map.js.map | |
- name: Compress web build .tar.gz and .zip | |
run: | | |
tar -czvf webBuild.tar.gz dist | |
zip -r webBuild.zip dist | |
- name: Upload .tar.gz web build artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'web' || 'web-staging' }}-build-tar-gz-artifact | |
path: ./webBuild.tar.gz | |
- name: Upload .zip web build artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'web' || 'web-staging' }}-build-zip-artifact | |
path: ./webBuild.zip | |
postSlackMessageOnFailure: | |
name: Post a Slack message when any platform fails to build or deploy | |
runs-on: ubuntu-latest | |
if: ${{ failure() }} | |
needs: [android, desktop, iOS, web] | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Post Slack message on failure | |
uses: ./.github/actions/composite/announceFailedWorkflowInSlack | |
with: | |
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
# Build a version of iOS and Android HybridApp if we are deploying to staging | |
hybridApp: | |
runs-on: ubuntu-latest | |
needs: prep | |
if: ${{ github.ref == 'refs/heads/staging' }} | |
steps: | |
- name: 'Deploy HybridApp' | |
run: gh workflow run --repo Expensify/Mobile-Deploy deploy.yml -f force_build=true -f build_version="${{ needs.prep.outputs.APP_VERSION }}" | |
env: | |
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} | |
checkDeploymentSuccess: | |
runs-on: ubuntu-latest | |
outputs: | |
IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }} | |
IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED }} | |
needs: [android, desktop, iOS, web] | |
if: ${{ always() }} | |
steps: | |
- name: Check deployment success on at least one platform | |
id: checkDeploymentSuccess | |
run: | | |
isAtLeastOnePlatformDeployed="false" | |
isAllPlatformsDeployed="false" | |
if [ "${{ needs.android.result }}" == "success" ] || \ | |
[ "${{ needs.iOS.result }}" == "success" ] || \ | |
[ "${{ needs.desktop.result }}" == "success" ] || \ | |
[ "${{ needs.web.result }}" == "success" ]; then | |
isAtLeastOnePlatformDeployed="true" | |
fi | |
if [ "${{ needs.android.result }}" == "success" ] && \ | |
[ "${{ needs.iOS.result }}" == "success" ] && \ | |
[ "${{ needs.desktop.result }}" == "success" ] && \ | |
[ "${{ needs.web.result }}" == "success" ]; then | |
isAllPlatformsDeployed="true" | |
fi | |
echo "IS_AT_LEAST_ONE_PLATFORM_DEPLOYED=\"$isAtLeastOnePlatformDeployed\"" >> "$GITHUB_OUTPUT" | |
echo "IS_ALL_PLATFORMS_DEPLOYED=\"$isAllPlatformsDeployed\"" >> "$GITHUB_OUTPUT" | |
createPrerelease: | |
runs-on: ubuntu-latest | |
if: ${{ github.ref == 'refs/heads/staging' && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} | |
needs: [prep, checkDeploymentSuccess] | |
steps: | |
- name: Download all workflow run artifacts | |
uses: actions/download-artifact@v4 | |
- name: 🚀 Create prerelease 🚀 | |
run: | | |
gh release create ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --title ${{ needs.prep.outputs.APP_VERSION }} --generate-notes --prerelease --target staging | |
RETRIES=0 | |
MAX_RETRIES=10 | |
until [[ $(gh release view ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }}) || $RETRIES -ge $MAX_RETRIES ]]; do | |
echo "release not found, retrying $((MAX_RETRIES - RETRIES++)) times" | |
sleep 1 | |
done | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
- name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name | |
run: | | |
mv ./desktop-staging-sourcemaps-artifact/merged-source-map.js.map ./desktop-staging-sourcemaps-artifact/desktop-staging-merged-source-map.js.map | |
mv ./web-staging-sourcemaps-artifact/merged-source-map.js.map ./web-staging-sourcemaps-artifact/web-staging-merged-source-map.js.map | |
- name: Upload artifacts to GitHub Release | |
run: | | |
gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \ | |
./android-sourcemaps-artifact/index.android.bundle.map#android-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./android-build-artifact/app-production-release.aab \ | |
./desktop-staging-sourcemaps-artifact/desktop-staging-merged-source-map.js.map#desktop-staging-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./desktop-staging-build-artifact/NewExpensify.dmg#NewExpensifyStaging.dmg \ | |
./ios-sourcemaps-artifact/main.jsbundle.map#ios-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./ios-build-artifact/New\ Expensify.ipa \ | |
./web-staging-sourcemaps-artifact/web-staging-merged-source-map.js.map#web-staging-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./web-staging-build-tar-gz-artifact/webBuild.tar.gz#stagingWebBuild.tar.gz \ | |
./web-staging-build-zip-artifact/webBuild.zip#stagingWebBuild.zip | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
- name: Warn deployers if staging deploy failed | |
if: ${{ failure() }} | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#deployer', | |
attachments: [{ | |
color: "#DB4545", | |
pretext: `<!subteam^S4TJJ3PSL>`, | |
text: `💥 NewDot staging deploy failed. 💥`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
finalizeRelease: | |
runs-on: ubuntu-latest | |
if: ${{ github.ref == 'refs/heads/production' && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} | |
needs: [prep, checkDeploymentSuccess] | |
steps: | |
- name: Download all workflow run artifacts | |
uses: actions/download-artifact@v4 | |
- name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name | |
run: | | |
mv ./desktop-sourcemaps-artifact/merged-source-map.js.map ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map | |
mv ./web-sourcemaps-artifact/merged-source-map.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map | |
- name: 🚀 Edit the release to be no longer a prerelease 🚀 | |
run: | | |
LATEST_RELEASE="$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName')" | |
gh api --method POST /repos/Expensify/App/releases/generate-notes -f "tag_name=${{ needs.prep.outputs.APP_VERSION }}" -f "previous_tag_name=$LATEST_RELEASE" | jq -r '.body' >> releaseNotes.md | |
gh release edit ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --prerelease=false --latest --notes-file releaseNotes.md | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
- name: Upload artifacts to GitHub Release | |
run: | | |
gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \ | |
./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map#desktop-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./desktop-build-artifact/NewExpensify.dmg \ | |
./web-sourcemaps-artifact/web-merged-source-map.js.map#web-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ | |
./web-build-tar-gz-artifact/webBuild.tar.gz \ | |
./web-build-zip-artifact/webBuild.zip | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
- name: Warn deployers if production deploy failed | |
if: ${{ failure() }} | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#deployer', | |
attachments: [{ | |
color: "#DB4545", | |
pretext: `<!subteam^S4TJJ3PSL>`, | |
text: `💥 NewDot production deploy failed. 💥`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
postSlackMessageOnSuccess: | |
name: Post a Slack message when all platforms deploy successfully | |
runs-on: ubuntu-latest | |
if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }} | |
needs: [prep, android, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] | |
steps: | |
- name: 'Announces the deploy in the #announce Slack room' | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#announce', | |
attachments: [{ | |
color: 'good', | |
text: `🎉️ Successfully deployed ${process.env.AS_REPO} <https://github.com/Expensify/App/releases/tag/${{ needs.prep.outputs.APP_VERSION }}|${{ needs.prep.outputs.APP_VERSION }}> to ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'production' || 'staging' }} 🎉️`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
- name: 'Announces the deploy in the #deployer Slack room' | |
uses: 8398a7/action-slack@v3 | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#deployer', | |
attachments: [{ | |
color: 'good', | |
text: `🎉️ Successfully deployed ${process.env.AS_REPO} <https://github.com/Expensify/App/releases/tag/${{ needs.prep.outputs.APP_VERSION }}|${{ needs.prep.outputs.APP_VERSION }}> to ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'production' || 'staging' }} 🎉️`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
- name: 'Announces a production deploy in the #expensify-open-source Slack room' | |
uses: 8398a7/action-slack@v3 | |
if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
with: | |
status: custom | |
custom_payload: | | |
{ | |
channel: '#expensify-open-source', | |
attachments: [{ | |
color: 'good', | |
text: `🎉️ Successfully deployed ${process.env.AS_REPO} <https://github.com/Expensify/App/releases/tag/${{ needs.prep.outputs.APP_VERSION }}|${{ needs.prep.outputs.APP_VERSION }}> to production 🎉️`, | |
}] | |
} | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} | |
postGithubComment: | |
name: Post a GitHub comments on all deployed PRs when platforms are done building and deploying | |
runs-on: ubuntu-latest | |
if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} | |
needs: [prep, android, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Setup Node | |
uses: ./.github/actions/composite/setupNode | |
- name: Get Release Pull Request List | |
id: getReleasePRList | |
uses: ./.github/actions/javascript/getDeployPullRequestList | |
with: | |
TAG: ${{ needs.prep.outputs.APP_VERSION }} | |
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} | |
IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
- name: Comment on issues | |
uses: ./.github/actions/javascript/markPullRequestsAsDeployed | |
with: | |
PR_LIST: ${{ steps.getReleasePRList.outputs.PR_LIST }} | |
IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} | |
DEPLOY_VERSION: ${{ needs.prep.outputs.APP_VERSION }} | |
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} | |
ANDROID: ${{ needs.android.result }} | |
DESKTOP: ${{ needs.desktop.result }} | |
IOS: ${{ needs.iOS.result }} | |
WEB: ${{ needs.web.result }} |