From 7aa7abaef071589b63b64323a0b8a90c7aa785d3 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Wed, 27 Nov 2024 14:56:53 +0100 Subject: [PATCH 1/8] Update build.gradle for hybrid app adhoc builds --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 901ef0ccbbbf..2b53f5a40423 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -199,7 +199,7 @@ afterEvaluate { def hermesCTask = gradle.includedBuild("react-native").task(":packages:react-native:ReactAndroid:hermes-engine:buildHermesC") android.applicationVariants.configureEach { variant -> - if (variant.buildType.name == "release") { + if (variant.buildType.name == "release" || variant.buildType.name == "adhoc") { def variantName = variant.name.capitalize() def bundleTask = tasks.named("createBundle${variantName}JsAndAssets").getOrNull() From 3ed499fabc6f2ab80cba1a740daae776169fc6d7 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Thu, 28 Nov 2024 09:49:33 +0100 Subject: [PATCH 2/8] Add new lane and new workflow --- .github/workflows/testBuildHybrid.yml | 352 ++++++++++++++++++++++++++ fastlane/Fastfile | 16 ++ 2 files changed, 368 insertions(+) create mode 100644 .github/workflows/testBuildHybrid.yml diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml new file mode 100644 index 000000000000..616256cd323a --- /dev/null +++ b/.github/workflows/testBuildHybrid.yml @@ -0,0 +1,352 @@ +name: Build and deploy hybird 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: + validateActor: + runs-on: ubuntu-latest + outputs: + READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} + steps: + - name: Is Expensify employee + id: isExpensifyEmployee + run: | + if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then + echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" + else + echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" + fi + env: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + + - id: hasReadyToBuildLabel + name: Set HAS_READY_TO_BUILD_LABEL flag + run: | + echo "HAS_READY_TO_BUILD_LABEL=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name' | grep -q 'Ready To Build' && echo 'true')" >> "$GITHUB_OUTPUT" + if [[ "$HAS_READY_TO_BUILD_LABEL" != 'true' ]]; then + echo "The 'Ready to Build' label is not attached to the PR #${{ env.PULL_REQUEST_NUMBER }}" + fi + env: + GITHUB_TOKEN: ${{ github.token }} + + getBranchRef: + runs-on: ubuntu-latest + needs: validateActor + if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + outputs: + REF: ${{ steps.getHeadRef.outputs.REF }} + steps: + - name: Checkout + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: actions/checkout@v4 + + - 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: ${{ secrets.GITHUB_TOKEN }} + + postGitHubCommentBuildStarted: + runs-on: ubuntu-latest + needs: [validateActor, getBranchRef] + if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + 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 hybrid app build. You can view the [workflow run here](${workflowURL}).` + }); + + androidHybrid: + name: Build Android HybridApp + needs: [validateActor, getBranchRef] + runs-on: ubuntu-latest-xl + defaults: + run: + working-directory: Mobile-Expensify/react-native + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: 'Expensify/Mobile-Expensify' + submodules: true + path: 'Mobile-Expensify' + token: ${{ secrets.OS_BOTIFY_TOKEN }} + # fetch-depth: 0 is required in order to fetch the correct submodule branch + fetch-depth: 0 + + - name: Update submodule + run: | + git submodule update --init + git fetch + git checkout ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + + - uses: actions/setup-node@v4 + with: + node-version-file: 'Mobile-Expensify/react-native/.nvmrc' + cache: npm + cache-dependency-path: 'Mobile-Expensify/react-native' + + - name: Setup dotenv + run: | + cp .env.staging .env.adhoc + sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=${{ inputs.pull_request_number }}" >> .env.adhoc + + - name: Install node modules + run: | + npm install + cd .. && npm install + + # Fixes https://github.com/Expensify/App/issues/51682 + npm run grunt:build:shared + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: '17' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1.190.0 + with: + bundler-cache: true + working-directory: 'Mobile-Expensify/react-native' + + - name: Install New Expensify Gems + run: bundle install + + - 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 document get --output ./upload-key.keystore upload-key.keystore + op document get --output ./android-fastlane-json-key.json android-fastlane-json-key.json + # Copy the keystore to the Android directory for Fullstory + cp ./upload-key.keystore ../Android + + - name: Load Android upload keystore credentials from 1Password + id: load-credentials + uses: 1password/load-secrets-action@v2 + with: + export-env: false + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + ANDROID_UPLOAD_KEYSTORE_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_PASSWORD + ANDROID_UPLOAD_KEYSTORE_ALIAS: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_ALIAS + ANDROID_UPLOAD_KEY_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEY_PASSWORD + + - 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 + run: bundle exec fastlane android build_adhoc_hybrid + env: + ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} + ANDROID_UPLOAD_KEYSTORE_ALIAS: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} + ANDROID_UPLOAD_KEY_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} + + uploadAndroid: + name: Upload Android hybrid app to S3 + needs: [androidHybrid] + 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/setup-ruby@v1.190.0 + 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: Log downloaded artifact paths + run: ls -R /tmp/artifacts + + - 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.androidHybrid.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: [validateActor, getBranchRef] + # if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + # env: + # DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer + # runs-on: macos-13-xlarge + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.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_15.2.0.app + + # - name: Setup Ruby + # uses: ruby/setup-ruby@v1.190.0 + # 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 AdHoc profile + # run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc.mobileprovision NewApp_AdHoc.mobileprovision.gpg + # env: + # LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + + # - name: Decrypt AdHoc Notification Service profile + # run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc_Notification_Service.mobileprovision NewApp_AdHoc_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: 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 + + postGithubComment: + runs-on: ubuntu-latest + name: Post a GitHub comment with app download links for testing + needs: [validateActor, getBranchRef, uploadAndroid] #TODO add ios job + if: ${{ always() }} + steps: + - name: Checkout + uses: actions/checkout@v4 + if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + with: + ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + + - name: Download Artifact + uses: actions/download-artifact@v4 + if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + + # - 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 + if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + uses: ./.github/actions/javascript/postTestBuildComment + with: + PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} + GITHUB_TOKEN: ${{ github.token }} + ANDROID: ${{ needs.uploadAndroid.result }} + IOS: 'success' + ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} + IOS_LINK: 'https://staging.new.expensify.com' diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 54084367040c..fa135b8c8a9d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -86,6 +86,22 @@ platform :android do setGradleOutputsInEnv() end + desc "Generate AdHoc HybridApp apk" + lane :build_adhoc_hybrid do + ENV["ENVFILE"]="../.env.adhoc.hybridapp" + gradle( + project_dir: '../Android', + task: 'assembleAdhoc', + properties: { + "android.injected.signing.store.file" => './upload-key.keystore', + "android.injected.signing.store.password" => ENV["ANDROID_UPLOAD_KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["ANDROID_UPLOAD_KEYSTORE_ALIAS"], + "android.injected.signing.key.password" => ENV["ANDROID_UPLOAD_KEY_PASSWORD"], + } + ) + setGradleOutputsInEnv() + end + desc "Generate a new local APK" lane :build_local do ENV["ENVFILE"]=".env.production" From c6bdf0b9f2f7247a3cffa144f788b2d99c16c43a Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Thu, 28 Nov 2024 13:26:43 +0100 Subject: [PATCH 3/8] remove commented ios workflow part --- .github/workflows/testBuildHybrid.yml | 103 -------------------------- 1 file changed, 103 deletions(-) diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 616256cd323a..9b39467d911d 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -221,98 +221,6 @@ jobs: # $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: [validateActor, getBranchRef] - # if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - # env: - # DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer - # runs-on: macos-13-xlarge - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - # with: - # ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.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_15.2.0.app - - # - name: Setup Ruby - # uses: ruby/setup-ruby@v1.190.0 - # 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 AdHoc profile - # run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc.mobileprovision NewApp_AdHoc.mobileprovision.gpg - # env: - # LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - # - name: Decrypt AdHoc Notification Service profile - # run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc_Notification_Service.mobileprovision NewApp_AdHoc_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: 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 - postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing @@ -329,17 +237,6 @@ jobs: uses: actions/download-artifact@v4 if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - # - 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 if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} uses: ./.github/actions/javascript/postTestBuildComment From bc599be2ec78810eed5ada12f7af7fcf405333cd Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Fri, 29 Nov 2024 12:31:13 +0100 Subject: [PATCH 4/8] Fix workflow --- .github/workflows/testBuildHybrid.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 9b39467d911d..3c1bced0a368 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -85,6 +85,8 @@ jobs: defaults: run: working-directory: Mobile-Expensify/react-native + outputs: + APK_FILE_NAME: ${{ steps.build.outputs.APK_FILE_NAME }} steps: - name: Checkout uses: actions/checkout@v4 @@ -168,11 +170,21 @@ jobs: run: echo "VERSION_CODE=$(grep -o 'versionCode\s\+[0-9]\+' android/app/build.gradle | awk '{ print $2 }')" >> "$GITHUB_OUTPUT" - name: Build Android app - run: bundle exec fastlane android build_adhoc_hybrid + id: build env: ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} ANDROID_UPLOAD_KEYSTORE_ALIAS: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} ANDROID_UPLOAD_KEY_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} + run: | + bundle exec fastlane android build_adhoc_hybrid + + # Refresh environment variables from GITHUB_ENV that are updated when running fastlane + # shellcheck disable=SC1090 + source "$GITHUB_ENV" + + # apkPath is set within the Fastfile + echo "APK_FILE_NAME=$(basename "$apkPath")" >> "$GITHUB_OUTPUT" + uploadAndroid: name: Upload Android hybrid app to S3 @@ -244,6 +256,7 @@ jobs: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} GITHUB_TOKEN: ${{ github.token }} ANDROID: ${{ needs.uploadAndroid.result }} - IOS: 'success' - ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} - IOS_LINK: 'https://staging.new.expensify.com' + DESKTOP: failed + IOS: failed + WEB: failed + ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} \ No newline at end of file From 7ad51a8665dc22d754a59695145aa45345c819f1 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Fri, 29 Nov 2024 13:26:04 +0100 Subject: [PATCH 5/8] Make platforms optional for publishing links --- .../javascript/postTestBuildComment/index.js | 53 +++++++++++------- .../postTestBuildComment.ts | 56 ++++++++++++------- .github/workflows/testBuildHybrid.yml | 3 - 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index aa0d0ec802b7..b8110f2152ec 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11502,31 +11502,42 @@ const github_1 = __nccwpck_require__(5438); const CONST_1 = __importDefault(__nccwpck_require__(9873)); const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); function getTestBuildMessage() { - console.log('Input for android', core.getInput('ANDROID', { required: true })); - const androidSuccess = core.getInput('ANDROID', { required: true }) === 'success'; - const desktopSuccess = core.getInput('DESKTOP', { required: true }) === 'success'; - const iOSSuccess = core.getInput('IOS', { required: true }) === 'success'; - const webSuccess = core.getInput('WEB', { required: true }) === 'success'; - const androidLink = androidSuccess ? core.getInput('ANDROID_LINK') : '❌ FAILED ❌'; - const desktopLink = desktopSuccess ? core.getInput('DESKTOP_LINK') : '❌ FAILED ❌'; - const iOSLink = iOSSuccess ? core.getInput('IOS_LINK') : '❌ FAILED ❌'; - const webLink = webSuccess ? core.getInput('WEB_LINK') : '❌ FAILED ❌'; - const androidQRCode = androidSuccess - ? `![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${androidLink})` - : "The QR code can't be generated, because the android build failed"; - const desktopQRCode = desktopSuccess - ? `![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${desktopLink})` - : "The QR code can't be generated, because the Desktop build failed"; - const iOSQRCode = iOSSuccess ? `![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${iOSLink})` : "The QR code can't be generated, because the iOS build failed"; - const webQRCode = webSuccess ? `![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${webLink})` : "The QR code can't be generated, because the web build failed"; + const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB']; + const names = { + [inputs[0]]: 'Android', + [inputs[1]]: 'Desktop', + [inputs[2]]: 'iOS', + [inputs[3]]: 'Web', + }; + const result = inputs.reduce((acc, platform) => { + const input = core.getInput(platform, { required: false }); + if (!input) { + return { + ...acc, + [platform]: { link: 'N/A', qrCode: 'N/A' }, + }; + } + const isSuccess = input === 'success'; + const link = isSuccess ? core.getInput(`${platform}_LINK`) : '❌ FAILED ❌'; + const qrCode = isSuccess + ? `![${names[platform]}](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${link})` + : `The QR code can't be generated, because the ${names[platform]} build failed`; + return { + ...acc, + [platform]: { + link, + qrCode, + }, + }; + }, {}); const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: | Android :robot: | iOS :apple: | | ------------- | ------------- | -| ${androidLink} | ${iOSLink} | -| ${androidQRCode} | ${iOSQRCode} | +| ${result['ANDROID'].link} | ${result['IOS'].link} | +| ${result['ANDROID'].qrCode} | ${result['IOS'].qrCode} | | Desktop :computer: | Web :spider_web: | -| ${desktopLink} | ${webLink} | -| ${desktopQRCode} | ${webQRCode} | +| ${result['DESKTOP'].link} | ${result['WEB'].link} | +| ${result['DESKTOP'].qrCode} | ${result['WEB'].qrCode} | --- diff --git a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts index 4fba1a6cb2ad..d85149c5a7e7 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts @@ -4,34 +4,48 @@ import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; function getTestBuildMessage(): string { - console.log('Input for android', core.getInput('ANDROID', {required: true})); - const androidSuccess = core.getInput('ANDROID', {required: true}) === 'success'; - const desktopSuccess = core.getInput('DESKTOP', {required: true}) === 'success'; - const iOSSuccess = core.getInput('IOS', {required: true}) === 'success'; - const webSuccess = core.getInput('WEB', {required: true}) === 'success'; + const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB'] as const; + const names = { + [inputs[0]]: 'Android', + [inputs[1]]: 'Desktop', + [inputs[2]]: 'iOS', + [inputs[3]]: 'Web', + }; - const androidLink = androidSuccess ? core.getInput('ANDROID_LINK') : '❌ FAILED ❌'; - const desktopLink = desktopSuccess ? core.getInput('DESKTOP_LINK') : '❌ FAILED ❌'; - const iOSLink = iOSSuccess ? core.getInput('IOS_LINK') : '❌ FAILED ❌'; - const webLink = webSuccess ? core.getInput('WEB_LINK') : '❌ FAILED ❌'; + const result = inputs.reduce((acc, platform) => { + const input = core.getInput(platform, {required: false}); - const androidQRCode = androidSuccess - ? `![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${androidLink})` - : "The QR code can't be generated, because the android build failed"; - const desktopQRCode = desktopSuccess - ? `![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${desktopLink})` - : "The QR code can't be generated, because the Desktop build failed"; - const iOSQRCode = iOSSuccess ? `![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${iOSLink})` : "The QR code can't be generated, because the iOS build failed"; - const webQRCode = webSuccess ? `![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${webLink})` : "The QR code can't be generated, because the web build failed"; + if (!input) { + return { + ...acc, + [platform]: {link: 'N/A', qrCode: 'N/A'}, + }; + } + + const isSuccess = input === 'success'; + + const link = isSuccess ? core.getInput(`${platform}_LINK`) : '❌ FAILED ❌'; + const qrCode = isSuccess + ? `![${names[platform]}](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${link})` + : `The QR code can't be generated, because the ${names[platform]} build failed`; + + return { + ...acc, + [platform]: { + link, + qrCode, + }, + }; + }, {} as Record<(typeof inputs)[number], {link: string; qrCode: string}>); const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: | Android :robot: | iOS :apple: | | ------------- | ------------- | -| ${androidLink} | ${iOSLink} | -| ${androidQRCode} | ${iOSQRCode} | +| ${result['ANDROID'].link} | ${result['IOS'].link} | +| ${result['ANDROID'].qrCode} | ${result['IOS'].qrCode} | | Desktop :computer: | Web :spider_web: | -| ${desktopLink} | ${webLink} | -| ${desktopQRCode} | ${webQRCode} | +| ${result['DESKTOP'].link} | ${result['WEB'].link} | +| ${result['DESKTOP'].qrCode} | ${result['WEB'].qrCode} | --- diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 3c1bced0a368..10131683df92 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -256,7 +256,4 @@ jobs: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} GITHUB_TOKEN: ${{ github.token }} ANDROID: ${{ needs.uploadAndroid.result }} - DESKTOP: failed - IOS: failed - WEB: failed ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} \ No newline at end of file From d7a73ca217891bb948ebe78d7b9ab09579401d1f Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 2 Dec 2024 09:45:14 +0100 Subject: [PATCH 6/8] Action linter --- .../javascript/postTestBuildComment/index.js | 8 +-- .../postTestBuildComment.ts | 11 ++-- tests/unit/postTestBuildComment.ts | 56 +++++++++++++++++-- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index b8110f2152ec..056981465e1e 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11533,11 +11533,11 @@ function getTestBuildMessage() { const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: | Android :robot: | iOS :apple: | | ------------- | ------------- | -| ${result['ANDROID'].link} | ${result['IOS'].link} | -| ${result['ANDROID'].qrCode} | ${result['IOS'].qrCode} | +| ${result.ANDROID.link} | ${result.IOS.link} | +| ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | | Desktop :computer: | Web :spider_web: | -| ${result['DESKTOP'].link} | ${result['WEB'].link} | -| ${result['DESKTOP'].qrCode} | ${result['WEB'].qrCode} | +| ${result.DESKTOP.link} | ${result.WEB.link} | +| ${result.DESKTOP.qrCode} | ${result.WEB.qrCode} | --- diff --git a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts index d85149c5a7e7..5b56a2420f37 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core'; import {context} from '@actions/github'; +import {TupleToUnion} from 'type-fest'; import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; @@ -36,16 +37,16 @@ function getTestBuildMessage(): string { qrCode, }, }; - }, {} as Record<(typeof inputs)[number], {link: string; qrCode: string}>); + }, {} as Record, {link: string; qrCode: string}>); const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: | Android :robot: | iOS :apple: | | ------------- | ------------- | -| ${result['ANDROID'].link} | ${result['IOS'].link} | -| ${result['ANDROID'].qrCode} | ${result['IOS'].qrCode} | +| ${result.ANDROID.link} | ${result.IOS.link} | +| ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | | Desktop :computer: | Web :spider_web: | -| ${result['DESKTOP'].link} | ${result['WEB'].link} | -| ${result['DESKTOP'].qrCode} | ${result['WEB'].qrCode} | +| ${result.DESKTOP.link} | ${result.WEB.link} | +| ${result.DESKTOP.qrCode} | ${result.WEB.qrCode} | --- diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 6a7f8182be1e..6d6815b9a600 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -62,18 +62,34 @@ const message = `:test_tube::test_tube: Use the links below to test this adhoc b :eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: `; +const onlyAndroidMessage = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: +| Android :robot: | iOS :apple: | +| ------------- | ------------- | +| ${androidLink} | N/A | +| ${androidQRCode} | N/A | +| Desktop :computer: | Web :spider_web: | +| N/A | N/A | +| N/A | N/A | + +--- + +:eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/1234) :eyes: +`; + describe('Post test build comments action tests', () => { beforeAll(() => { // Mock core module asMutable(core).getInput = mockGetInput; }); + beforeEach(() => jest.clearAllMocks()); + test('Test GH action', async () => { when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue('12'); - when(core.getInput).calledWith('ANDROID', {required: true}).mockReturnValue('success'); - when(core.getInput).calledWith('IOS', {required: true}).mockReturnValue('success'); - when(core.getInput).calledWith('WEB', {required: true}).mockReturnValue('success'); - when(core.getInput).calledWith('DESKTOP', {required: true}).mockReturnValue('success'); + when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); + when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue('success'); + when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue('success'); + when(core.getInput).calledWith('DESKTOP', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('ANDROID_LINK').mockReturnValue('https://expensify.app/ANDROID_LINK'); when(core.getInput).calledWith('IOS_LINK').mockReturnValue('https://expensify.app/IOS_LINK'); when(core.getInput).calledWith('WEB_LINK').mockReturnValue('https://expensify.app/WEB_LINK'); @@ -102,4 +118,36 @@ describe('Post test build comments action tests', () => { expect(createCommentMock).toBeCalledTimes(1); expect(createCommentMock).toBeCalledWith('App', 12, message); }); + + test('Test GH action when input is not complete', async () => { + when(core.getInput).calledWith('PR_NUMBER', {required: true}).mockReturnValue('12'); + when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); + when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue(''); + when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue(''); + when(core.getInput).calledWith('DESKTOP', {required: false}).mockReturnValue(''); + when(core.getInput).calledWith('ANDROID_LINK').mockReturnValue('https://expensify.app/ANDROID_LINK'); + createCommentMock.mockResolvedValue({} as CreateCommentResponse); + mockListComments.mockResolvedValue({ + data: [ + { + body: ':test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing!', + // eslint-disable-next-line @typescript-eslint/naming-convention + node_id: 'IC_abcd', + }, + ], + }); + await ghAction(); + expect(mockGraphql).toBeCalledTimes(1); + expect(mockGraphql).toBeCalledWith(` + mutation { + minimizeComment(input: {classifier: OUTDATED, subjectId: "IC_abcd"}) { + minimizedComment { + minimizedReason + } + } + } + `); + expect(createCommentMock).toBeCalledTimes(1); + expect(createCommentMock).toBeCalledWith('App', 12, onlyAndroidMessage); + }); }); From 17d78c716bb8a119ade9adebf8ae50e4e748acc9 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 2 Dec 2024 10:09:46 +0100 Subject: [PATCH 7/8] Fix linter again --- .../javascript/postTestBuildComment/index.js | 16 ++++++---------- .../postTestBuildComment.ts | 18 +++++++----------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index 056981465e1e..8906bb597d63 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11512,23 +11512,19 @@ function getTestBuildMessage() { const result = inputs.reduce((acc, platform) => { const input = core.getInput(platform, { required: false }); if (!input) { - return { - ...acc, - [platform]: { link: 'N/A', qrCode: 'N/A' }, - }; + acc[platform] = { link: 'N/A', qrCode: 'N/A' }; + return acc; } const isSuccess = input === 'success'; const link = isSuccess ? core.getInput(`${platform}_LINK`) : '❌ FAILED ❌'; const qrCode = isSuccess ? `![${names[platform]}](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${link})` : `The QR code can't be generated, because the ${names[platform]} build failed`; - return { - ...acc, - [platform]: { - link, - qrCode, - }, + acc[platform] = { + link, + qrCode, }; + return acc; }, {}); const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: | Android :robot: | iOS :apple: | diff --git a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts index 5b56a2420f37..813665f2dff5 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import {context} from '@actions/github'; -import {TupleToUnion} from 'type-fest'; +import type {TupleToUnion} from 'type-fest'; import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; @@ -17,10 +17,8 @@ function getTestBuildMessage(): string { const input = core.getInput(platform, {required: false}); if (!input) { - return { - ...acc, - [platform]: {link: 'N/A', qrCode: 'N/A'}, - }; + acc[platform] = {link: 'N/A', qrCode: 'N/A'}; + return acc; } const isSuccess = input === 'success'; @@ -30,13 +28,11 @@ function getTestBuildMessage(): string { ? `![${names[platform]}](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${link})` : `The QR code can't be generated, because the ${names[platform]} build failed`; - return { - ...acc, - [platform]: { - link, - qrCode, - }, + acc[platform] = { + link, + qrCode, }; + return acc; }, {} as Record, {link: string; qrCode: string}>); const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube: From b15d24cdf8aaec04085044ec72f0ba3aff99d709 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 2 Dec 2024 14:02:33 +0100 Subject: [PATCH 8/8] Fix linter --- .../actions/javascript/postTestBuildComment/action.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/javascript/postTestBuildComment/action.yml b/.github/actions/javascript/postTestBuildComment/action.yml index 00c826badf9f..d6c3391f3c26 100644 --- a/.github/actions/javascript/postTestBuildComment/action.yml +++ b/.github/actions/javascript/postTestBuildComment/action.yml @@ -3,22 +3,22 @@ description: "Mark pull requests as deployed on production or staging" inputs: PR_NUMBER: description: "Pull request number" - required: true + required: false GITHUB_TOKEN: description: "Github token for authentication" default: "${{ github.token }}" ANDROID: description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')" - required: true + required: false DESKTOP: description: "Desktop job result ('success', 'failure', 'cancelled', or 'skipped')" - required: true + required: false IOS: description: "iOS job result ('success', 'failure', 'cancelled', or 'skipped')" - required: true + required: false WEB: description: "Web job result ('success', 'failure', 'cancelled', or 'skipped')" - required: true + required: false ANDROID_LINK: description: "Link for the Android build" required: false