diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml index 00fb5d7283f3..c12ef3636066 100644 --- a/.github/actions/composite/setupNode/action.yml +++ b/.github/actions/composite/setupNode/action.yml @@ -6,6 +6,10 @@ inputs: description: "Indicates if node is set up for hybrid app" required: false default: 'false' + IS_DESKTOP_BUILD: + description: "Indicates if node is set up for desktop app" + required: false + default: 'false' outputs: cache-hit: @@ -23,7 +27,9 @@ runs: with: node-version-file: '.nvmrc' cache: npm - cache-dependency-path: normalized-package-lock.json + cache-dependency-path: | + normalized-package-lock.json + desktop/package-lock.json - id: cache-node-modules # v4 @@ -40,6 +46,14 @@ runs: path: Mobile-Expensify/node_modules key: ${{ runner.os }}-node-modules-${{ hashFiles('Mobile-Expensify/package-lock.json', 'Mobile-Expensify/patches/**') }} + - id: cache-desktop-node-modules + if: inputs.IS_DESKTOP_BUILD == 'true' + # v4 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 + with: + path: desktop/node_modules + key: ${{ runner.os }}-desktop-node-modules-${{ hashFiles('desktop/package-lock.json', 'desktop/patches/**') }} + - name: Remove ND node_modules if needed for hybrid app build if: inputs.IS_HYBRID_BUILD == 'true' && steps.cache-node-modules.outputs.cache-hit == 'true' && steps.cache-old-dot-node-modules.outputs.cache-hit != 'true' shell: bash @@ -51,4 +65,12 @@ runs: with: timeout_minutes: 30 max_attempts: 3 - command: npm ci \ No newline at end of file + command: npm ci + + - name: Install node packages for desktop submodule + if: inputs.IS_DESKTOP_BUILD == 'true' && steps.cache-desktop-node-modules.outputs.cache-hit != 'true' + uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 + with: + timeout_minutes: 30 + max_attempts: 3 + command: cd desktop && npm ci diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml index b8a39af6c3ae..f7af5e586968 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml +++ b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml @@ -20,6 +20,9 @@ inputs: ANDROID: description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')" required: true + DESKTOP: + description: "Desktop job result ('success', 'failure', 'cancelled', or 'skipped')" + required: true IOS: description: "iOS job result ('success', 'failure', 'cancelled', or 'skipped')" required: true diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index a13c54e6b1ae..d8d7a926f00b 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -12840,6 +12840,7 @@ async function run() { const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', { required: true }); const version = core.getInput('DEPLOY_VERSION', { required: true }); const androidResult = getDeployTableMessage(core.getInput('ANDROID', { required: true })); + const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', { required: true })); const iOSResult = getDeployTableMessage(core.getInput('IOS', { required: true })); const webResult = getDeployTableMessage(core.getInput('WEB', { required: true })); const date = core.getInput('DATE'); @@ -12851,7 +12852,7 @@ async function run() { message += `on ${date}`; } message += `šŸš€`; - message += `\n\nplatform | result\n---|---`; + message += `\n\nplatform | result\n---|---\nšŸ–„ desktop šŸ–„|${desktopResult}`; message += `\nšŸ•ø web šŸ•ø|${webResult}`; message += `\nšŸ¤– android šŸ¤–|${androidResult}\nšŸŽ iOS šŸŽ|${iOSResult}`; if (note) { diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts index bf7ce3c35f44..93131cb8125e 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts +++ b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts @@ -103,6 +103,7 @@ async function run() { const version = core.getInput('DEPLOY_VERSION', {required: true}); const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true}) as PlatformResult); + const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true}) as PlatformResult); const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true}) as PlatformResult); const webResult = getDeployTableMessage(core.getInput('WEB', {required: true}) as PlatformResult); @@ -116,7 +117,7 @@ async function run() { message += `on ${date}`; } message += `šŸš€`; - message += `\n\nplatform | result\n---|---`; + message += `\n\nplatform | result\n---|---\nšŸ–„ desktop šŸ–„|${desktopResult}`; message += `\nšŸ•ø web šŸ•ø|${webResult}`; message += `\nšŸ¤– android šŸ¤–|${androidResult}\nšŸŽ iOS šŸŽ|${iOSResult}`; diff --git a/.github/actions/javascript/postTestBuildComment/action.yml b/.github/actions/javascript/postTestBuildComment/action.yml index ea2c75475f9e..ad97aa9015f4 100644 --- a/.github/actions/javascript/postTestBuildComment/action.yml +++ b/.github/actions/javascript/postTestBuildComment/action.yml @@ -16,6 +16,9 @@ inputs: ANDROID: description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')" required: false + DESKTOP: + description: "Desktop job result ('success', 'failure', 'cancelled', or 'skipped')" + required: false IOS: description: "iOS job result ('success', 'failure', 'cancelled', or 'skipped')" required: false @@ -25,6 +28,9 @@ inputs: ANDROID_LINK: description: "Link for the Android build" required: false + DESKTOP_LINK: + description: "Link for the desktop build" + required: false IOS_LINK: description: "Link for the iOS build" required: false diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index 3a31cd257a72..eb1ade0901c4 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -11585,11 +11585,12 @@ const github_1 = __nccwpck_require__(5438); const CONST_1 = __importDefault(__nccwpck_require__(9873)); const GithubUtils_1 = __importDefault(__nccwpck_require__(9296)); function getTestBuildMessage(appPr, mobileExpensifyPr) { - const inputs = ['ANDROID', 'IOS', 'WEB']; + const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB']; const names = { [inputs[0]]: 'Android', - [inputs[1]]: 'iOS', - [inputs[2]]: 'Web', + [inputs[1]]: 'Desktop', + [inputs[2]]: 'iOS', + [inputs[3]]: 'Web', }; const result = inputs.reduce((acc, platform) => { const input = core.getInput(platform, { required: false }); @@ -11618,17 +11619,17 @@ function getTestBuildMessage(appPr, mobileExpensifyPr) { }; return acc; }, {}); - const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS${appPr ? ', and Web' : ''}. Happy testing! :test_tube::test_tube: + const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS${appPr ? ', Desktop, and Web' : ''}. Happy testing! :test_tube::test_tube: Built from${appPr ? ` App PR Expensify/App#${appPr}` : ''}${mobileExpensifyPr ? ` Mobile-Expensify PR Expensify/Mobile-Expensify#${mobileExpensifyPr}` : ''}. | Android :robot: | iOS :apple: | | ------------- | ------------- | | ${result.ANDROID.link} | ${result.IOS.link} | | ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | -| Web :spider_web: | -| ------------- | -| ${result.WEB.link} | -| ${result.WEB.qrCode} | +| Desktop :computer: | Web :spider_web: | +| ------------- | ------------- | +| ${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 a2278e10fce5..8a825cf8e09c 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts @@ -5,11 +5,12 @@ import CONST from '@github/libs/CONST'; import GithubUtils from '@github/libs/GithubUtils'; function getTestBuildMessage(appPr?: number, mobileExpensifyPr?: number): string { - const inputs = ['ANDROID', 'IOS', 'WEB'] as const; + const inputs = ['ANDROID', 'DESKTOP', 'IOS', 'WEB'] as const; const names = { [inputs[0]]: 'Android', - [inputs[1]]: 'iOS', - [inputs[2]]: 'Web', + [inputs[1]]: 'Desktop', + [inputs[2]]: 'iOS', + [inputs[3]]: 'Web', }; const result = inputs.reduce( @@ -46,17 +47,17 @@ function getTestBuildMessage(appPr?: number, mobileExpensifyPr?: number): 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${appPr ? ', and Web' : ''}. Happy testing! :test_tube::test_tube: + const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS${appPr ? ', Desktop, and Web' : ''}. Happy testing! :test_tube::test_tube: Built from${appPr ? ` App PR Expensify/App#${appPr}` : ''}${mobileExpensifyPr ? ` Mobile-Expensify PR Expensify/Mobile-Expensify#${mobileExpensifyPr}` : ''}. | Android :robot: | iOS :apple: | | ------------- | ------------- | | ${result.ANDROID.link} | ${result.IOS.link} | | ${result.ANDROID.qrCode} | ${result.IOS.qrCode} | -| Web :spider_web: | -| ------------- | -| ${result.WEB.link} | -| ${result.WEB.qrCode} | +| Desktop :computer: | Web :spider_web: | +| ------------- | ------------- | +| ${result.DESKTOP.link} | ${result.WEB.link} | +| ${result.DESKTOP.qrCode} | ${result.WEB.qrCode} | --- diff --git a/.github/workflows/checkE2ETestCode.yml b/.github/workflows/checkE2ETestCode.yml index 25c2be16c28b..60ca53e2699f 100644 --- a/.github/workflows/checkE2ETestCode.yml +++ b/.github/workflows/checkE2ETestCode.yml @@ -19,6 +19,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: 'true' - name: Verify e2e tests compile correctly run: npm run e2e-test-runner-build diff --git a/.github/workflows/checkSVGCompression.yml b/.github/workflows/checkSVGCompression.yml index 534c5aaf367d..5947a04a454d 100644 --- a/.github/workflows/checkSVGCompression.yml +++ b/.github/workflows/checkSVGCompression.yml @@ -26,6 +26,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Compress SVG check uses: ./.github/actions/javascript/checkSVGCompression diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 875e8fabdc2c..7444ef53f006 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -287,6 +287,70 @@ jobs: 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 + # v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true + + # v6.0.0 + - name: Setup Python for node-gyp + id: setup-python + uses: actions/setup-python@18566f86b301499665bd3eb1a2247e0849c64fa5 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: desktop/requirements.txt + + - name: Ensure setuptools for node-gyp + run: pip install --upgrade -r desktop/requirements.txt + + - name: Load Desktop 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 }} + DESKTOP_CERTIFICATE_BASE64: "op://${{ vars.OP_VAULT }}/Desktop Certificates.p12/CSC_LINK" + DESKTOP_CERTIFICATE_PASSWORD: "op://${{ vars.OP_VAULT }}/Desktop Certificates.p12/CSC_KEY_PASSWORD" + + - name: Build desktop app + run: ${{ github.ref == 'refs/heads/production' && 'npm run desktop-build' || 'npm run desktop-build-staging' }} + env: + CSC_LINK: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_BASE64 }} + CSC_KEY_PASSWORD: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_PASSWORD }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} + S3_BUCKET: ${{ github.ref == 'refs/heads/production' && vars.PRODUCTION_S3_BUCKET || vars.STAGING_S3_BUCKET }} + + - name: Upload desktop sourcemaps artifact + # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: desktop-sourcemaps-artifact + path: ./desktop/dist/www/merged-source-map.js.map + + - name: Upload desktop build artifact + # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: desktop-build-artifact + path: ./desktop-build/NewExpensify.dmg + ios: name: Build and deploy iOS HybridApp needs: prep @@ -551,7 +615,7 @@ jobs: name: Post a Slack message when any platform fails to build or deploy runs-on: ubuntu-latest if: ${{ failure() }} - needs: [android, ios, web] + needs: [android, desktop, ios, web] steps: - name: Checkout # v4 @@ -567,7 +631,7 @@ jobs: outputs: IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAtLeastOnePlatform.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }} IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAllPlatforms.outputs.IS_ALL_PLATFORMS_DEPLOYED }} - needs: [android, ios, web] + needs: [android, desktop, ios, web] if: ${{ always() }} steps: - name: Check deployment success on at least one platform @@ -576,6 +640,7 @@ jobs: isAtLeastOnePlatformDeployed="false" if [ "${{ needs.iOS.result }}" == "success" ] || \ [ "${{ needs.android.result }}" == "success" ] || \ + [ "${{ needs.desktop.result }}" == "success" ] || \ [ "${{ needs.web.result }}" == "success" ]; then isAtLeastOnePlatformDeployed="true" fi @@ -588,6 +653,7 @@ jobs: isAllPlatformsDeployed="false" if [ "${{ needs.iOS.result }}" == "success" ] && \ [ "${{ needs.android.result }}" == "success" ] && \ + [ "${{ needs.desktop.result }}" == "success" ] && \ [ "${{ needs.web.result }}" == "success" ]; then isAllPlatformsDeployed="true" fi @@ -635,9 +701,10 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} - - name: Rename web sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name + - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name continue-on-error: true 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: Upload artifacts to GitHub Release @@ -648,6 +715,8 @@ jobs: ./android-build-artifact/Expensify-release.aab#android.aab ./android-apk-artifact/Expensify.apk#android.apk ./android-sourcemap-artifact/index.android.bundle.map#android-sourcemap.js.map + ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map#desktop-sourcemap.js.map + ./desktop-build-artifact/NewExpensify.dmg#desktop.dmg ./ios-build-artifact/Expensify.ipa#ios.ipa ./ios-sourcemap-artifact/main.jsbundle.map#ios-sourcemap.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map#web-sourcemap.js.map @@ -707,7 +776,7 @@ jobs: 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, ios, web, checkDeploymentSuccess, createRelease] + needs: [prep, android, desktop, ios, web, checkDeploymentSuccess, createRelease] steps: - name: 'Announces the deploy in the #announce Slack room' # v3 @@ -764,7 +833,7 @@ jobs: postGithubComments: uses: ./.github/workflows/postDeployComments.yml if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} - needs: [prep, android, ios, web, checkDeploymentSuccess, createRelease] + needs: [prep, android, desktop, ios, web, checkDeploymentSuccess, createRelease] secrets: inherit with: version: ${{ needs.prep.outputs.APP_VERSION }} @@ -772,3 +841,4 @@ jobs: android: ${{ needs.android.result }} ios: ${{ needs.ios.result }} web: ${{ needs.web.result }} + desktop: ${{ needs.desktop.result }} diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 822deea3edd6..de82e002c96b 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -113,6 +113,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: 'true' - name: Make zip directory for everything to send to AWS Device Farm run: mkdir zip diff --git a/.github/workflows/lint-changed.yml b/.github/workflows/lint-changed.yml index 00f05209cfa7..7bba2072bdd6 100644 --- a/.github/workflows/lint-changed.yml +++ b/.github/workflows/lint-changed.yml @@ -54,6 +54,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Run ESLint to check for deprecation warnings run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index baa630c61ef8..fe3855db4b54 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,6 +23,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Lint JavaScript and Typescript with ESLint run: npm run lint diff --git a/.github/workflows/postDeployComments.yml b/.github/workflows/postDeployComments.yml index 35123e3d1504..1b653085c114 100644 --- a/.github/workflows/postDeployComments.yml +++ b/.github/workflows/postDeployComments.yml @@ -23,6 +23,10 @@ on: description: Web deploy status required: true type: string + desktop: + description: Desktop deploy status + required: true + type: string secrets: OS_BOTIFY_TOKEN: description: Token for accessing Mobile-Expensify repository @@ -67,6 +71,15 @@ on: - failure - cancelled - skipped + desktop: + description: Desktop deploy status + required: true + type: choice + options: + - success + - failure + - cancelled + - skipped date: description: The date when this deploy occurred required: false @@ -105,6 +118,7 @@ jobs: DEPLOY_VERSION: ${{ inputs.version }} GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} ANDROID: ${{ inputs.android }} + DESKTOP: ${{ inputs.desktop }} IOS: ${{ inputs.ios }} WEB: ${{ inputs.web }} DATE: ${{ inputs.date }} diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 12af5722e60e..5b68e033ee66 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -23,6 +23,8 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Verify there's no Prettier diff run: | diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 73b4dbe059b2..3f8a83213502 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -22,6 +22,10 @@ on: description: Should build web app? type: boolean default: true + DESKTOP: + description: Should build desktop app? + type: boolean + default: true IOS: description: Should build iOS app? type: boolean @@ -158,6 +162,73 @@ jobs: body: `🚧 @${{ github.actor }} has triggered a test Expensify/Mobile-Expensify build. You can view the [workflow run here](${workflowURL}).` }); + desktop: + name: Build and deploy Desktop for testing + if: ${{ inputs.DESKTOP && inputs.APP_PULL_REQUEST_NUMBER }} + needs: [prep] + runs-on: macos-14-large + steps: + - name: Checkout + # v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + with: + ref: ${{ needs.prep.outputs.APP_REF }} + + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + run: | + cp .env.staging .env.adhoc + sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true + + # v6.0.0 + - name: Setup python for node-gyp + id: setup-python + uses: actions/setup-python@18566f86b301499665bd3eb1a2247e0849c64fa5 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: desktop/requirements.txt + + - name: Ensure setuptools for node-gyp + run: pip install --upgrade -r desktop/requirements.txt + + - name: Load Desktop 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 }} + DESKTOP_CERTIFICATE_BASE64: "op://${{ vars.OP_VAULT }}/Desktop Certificates.p12/CSC_LINK" + DESKTOP_CERTIFICATE_PASSWORD: "op://${{ vars.OP_VAULT }}/Desktop Certificates.p12/CSC_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: Build desktop app for testing + run: npm run desktop-build-adhoc + env: + CSC_LINK: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_BASE64 }} + CSC_KEY_PASSWORD: ${{ steps.load-credentials.outputs.DESKTOP_CERTIFICATE_PASSWORD }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_STAGING }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + web: name: Build and deploy Web if: ${{ inputs.WEB && inputs.APP_PULL_REQUEST_NUMBER }} @@ -427,7 +498,7 @@ jobs: runs-on: ubuntu-latest if: always() name: Post a GitHub comment with app download links for testing - needs: [prep, getMobileExpensifyPR, web, androidHybrid, iosHybrid] + needs: [prep, getMobileExpensifyPR, desktop, web, androidHybrid, iosHybrid] steps: - name: Checkout # v4 @@ -448,9 +519,11 @@ jobs: MOBILE_EXPENSIFY_PR_NUMBER: ${{ needs.getMobileExpensifyPR.outputs.MOBILE_EXPENSIFY_PR }} GITHUB_TOKEN: ${{ github.token }} ANDROID: ${{ needs.androidHybrid.result }} + DESKTOP: ${{ needs.desktop.result }} IOS: ${{ needs.iosHybrid.result }} WEB: ${{ needs.web.result }} ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }} + DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ inputs.APP_PULL_REQUEST_NUMBER }}/NewExpensify.dmg IOS_LINK: ${{ needs.iosHybrid.outputs.IOS_PATH }} WEB_LINK: https://${{ inputs.APP_PULL_REQUEST_NUMBER }}.pr-testing.expensify.com diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 6d48f9a933c8..c675f3a3b5a2 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -19,6 +19,8 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: ./.github/actions/composite/setupNode + with: + IS_DESKTOP_BUILD: true - name: Type check with TypeScript run: npm run typecheck diff --git a/contributingGuides/SETUP_WEB.md b/contributingGuides/SETUP_WEB.md index 533af3b30741..db25bc371d23 100644 --- a/contributingGuides/SETUP_WEB.md +++ b/contributingGuides/SETUP_WEB.md @@ -55,7 +55,7 @@ Creating an `.env` file is not necessary. We advise external contributors agains - `EXPENSIFY_PARTNER_NAME` - Constant used for the app when authenticating. - `EXPENSIFY_PARTNER_PASSWORD` - Another constant used for the app when authenticating. (This is OK to be public) - `PUSHER_APP_KEY` - Key used to authenticate with Pusher.com -- `USE_WEB_PROXY`āš ļø- Used in web development, it starts a server along the local development server to proxy requests to the backend. External contributors should set this to `true` otherwise they'll have CORS errors. If you don't want to start the proxy server set this explicitly to `false` +- `USE_WEB_PROXY`āš ļø- Used in web/desktop development, it starts a server along the local development server to proxy requests to the backend. External contributors should set this to `true` otherwise they'll have CORS errors. If you don't want to start the proxy server set this explicitly to `false` ### Optional Performance Variables - `CAPTURE_METRICS` (optional) - Set this to `true` to capture performance metrics and see them in Flipper. See [PERFORMANCE.md](contributingGuides/PERFORMANCE.md#performance-metrics-opt-in-on-local-release-builds) for more information diff --git a/contributingGuides/philosophies/DEPLOYING.md b/contributingGuides/philosophies/DEPLOYING.md index 69f6c0fb6c4b..584ed5fdecef 100644 --- a/contributingGuides/philosophies/DEPLOYING.md +++ b/contributingGuides/philosophies/DEPLOYING.md @@ -53,7 +53,7 @@ The [`deploy` workflow](https://github.com/Expensify/App/blob/main/.github/workf - If `production` was updated, it creates a GitHub Release for the new version. ### platformDeploy -The [`platformDeploy` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/platformDeploy.yml) is what actually runs the deployment on all three platforms (iOS, Android, Web). It runs a staging deploy whenever a new tag is pushed to GitHub, and runs a production deploy whenever a new release is created. +The [`platformDeploy` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/platformDeploy.yml) is what actually runs the deployment on all four platforms (iOS, Android, Web, macOS Desktop). It runs a staging deploy whenever a new tag is pushed to GitHub, and runs a production deploy whenever a new release is created. ### lockDeploys The [`lockDeploys` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/lockDeploys.yml) executes when the `StagingDeployCash` is locked, and it waits for any currently running staging deploys to finish, then gives Applause the :green_circle: to begin QA by commenting in the `StagingDeployCash` checklist. @@ -62,7 +62,7 @@ The [`lockDeploys` workflow](https://github.com/Expensify/App/blob/main/.github/ The [`finishReleaseCycle` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/finishReleaseCycle.yml) executes when the `StagingDeployCash` is closed. It updates the `production` branch from `staging` (triggering a production deploy), deploys `main` to staging (with a new `PATCH` version), and creates a new `StagingDeployCash` deploy checklist. ### testBuild -The [`testBuild` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/testBuild.yml) builds ad-hoc staging apps (hybrid iOS, hybrid Android, and web) from pull requests submitted to the App and Mobile-Expensify repositories. This process enables testers to review modifications before they are merged into the main branch and deployed to the staging environment. This workflow accepts up to two inputs: +The [`testBuild` workflow](https://github.com/Expensify/App/blob/main/.github/workflows/testBuild.yml) builds ad-hoc staging apps (hybrid iOS, hybrid Android, web, and desktop) from pull requests submitted to the App and Mobile-Expensify repositories. This process enables testers to review modifications before they are merged into the main branch and deployed to the staging environment. This workflow accepts up to two inputs: - A PR number from the App repository for testing New Dot (ND) changes. - A PR number from the Mobile-Expensify repository for testing Old Dot (OD) changes. @@ -75,6 +75,26 @@ Sometimes it might be beneficial to generate a local production version instead ### - Web app local production builds SHOULD use `npm run build` In order to generate a production web build, run `npm run build`, this will generate a production javascript build in the `dist/` folder. +### - MacOS desktop app local production builds SHOULD disable publishing for local testing +The commands used to compile a production or staging desktop build are `npm run desktop-build` and `npm run desktop-build-staging`, respectively. These will produce an app in the `dist/Mac` folder named NewExpensify.dmg that you can install like a normal app. + +HOWEVER, by default those commands will try to notarize the build (signing it as Expensify) and publish it to the S3 bucket where it's hosted for users. In most cases you won't actually need or want to do that for your local testing. To get around that and disable those behaviors for your local build, apply the following diff: + +```diff +diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh +index 791f59d733..526306eec1 100755 +--- a/scripts/build-desktop.sh ++++ b/scripts/build-desktop.sh +@@ -35,4 +35,4 @@ npx webpack --config config/webpack/webpack.desktop.ts --env file=$ENV_FILE + title "Building Desktop App Archive Using Electron" + info "" + shift 1 +-npx electron-builder --config config/electronBuilder.config.js --publish always "$@" ++npx electron-builder --config config/electronBuilder.config.js --publish never "$@" +``` + +There may be some cases where you need to test a signed and published build, such as when testing the update flows. Instructions on setting that up can be found in [Testing Electron Auto-Update](https://github.com/Expensify/App/blob/main/desktop/README.md#testing-electron-auto-update). Good luck šŸ™ƒ + ### - iOS app local production builds SHOULD use `npm run ios-build` In order to compile a production iOS build, run `npm run ios-build`, this will generate a `Chat.ipa` in the root directory of this project. diff --git a/eslint.config.mjs b/eslint.config.mjs index cf4b5ce55c10..a980f1107434 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -598,7 +598,6 @@ const config = defineConfig([ '**/vendor', 'modules/group-ib-fp/**/*', 'web/snippets/gib.js', - 'desktop/**/*', ]), ]); diff --git a/package.json b/package.json index c48e6ad15fc8..a455eee698d5 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,12 @@ "build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production && ts-node ./scripts/combine-web-sourcemaps.ts", "build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging && ts-node ./scripts/combine-web-sourcemaps.ts", "build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc && ts-node ./scripts/combine-web-sourcemaps.ts", + "desktop": "./scripts/set-pusher-suffix.sh && ts-node desktop/start.ts", + "desktop-build": "./scripts/build-desktop.sh production", + "desktop-build-staging": "./scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.ts", "detectRedirectCycle": "ts-node .github/scripts/detectRedirectCycle.ts", + "desktop-build-adhoc": "./scripts/build-desktop.sh adhoc", "ios-build": "bundle exec fastlane ios build_unsigned", "ios-hybrid-build": "bundle exec fastlane ios build_unsigned_hybrid", "android-build": "bundle exec fastlane android build_local", diff --git a/tests/unit/markPullRequestsAsDeployedTest.ts b/tests/unit/markPullRequestsAsDeployedTest.ts index 2f19afc2cdfc..fb29e697f1e3 100644 --- a/tests/unit/markPullRequestsAsDeployedTest.ts +++ b/tests/unit/markPullRequestsAsDeployedTest.ts @@ -81,6 +81,7 @@ function mockGetInputDefaultImplementation(key: string): boolean | string { return version; case 'IOS': case 'ANDROID': + case 'DESKTOP': case 'WEB': return 'success'; case 'DATE': @@ -191,6 +192,7 @@ describe('markPullRequestsAsDeployed', () => { platform | result ---|--- +šŸ–„ desktop šŸ–„|success āœ… šŸ•ø web šŸ•ø|success āœ… šŸ¤– android šŸ¤–|success āœ… šŸŽ iOS šŸŽ|success āœ…`, @@ -220,6 +222,7 @@ platform | result platform | result ---|--- +šŸ–„ desktop šŸ–„|success āœ… šŸ•ø web šŸ•ø|success āœ… šŸ¤– android šŸ¤–|success āœ… šŸŽ iOS šŸŽ|success āœ…`, @@ -278,6 +281,7 @@ platform | result platform | result ---|--- +šŸ–„ desktop šŸ–„|success āœ… šŸ•ø web šŸ•ø|success āœ… šŸ¤– android šŸ¤–|success āœ… šŸŽ iOS šŸŽ|success āœ…`, @@ -295,6 +299,9 @@ platform | result if (key === 'IOS') { return 'failed'; } + if (key === 'DESKTOP') { + return 'cancelled'; + } return mockGetInputDefaultImplementation(key); }); @@ -309,6 +316,7 @@ platform | result platform | result ---|--- +šŸ–„ desktop šŸ–„|cancelled šŸ”Ŗ šŸ•ø web šŸ•ø|success āœ… šŸ¤– android šŸ¤–|skipped 🚫 šŸŽ iOS šŸŽ|failed āŒ`, diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index f7caf2dc6f61..137bf8def350 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -46,39 +46,41 @@ jest.mock('@actions/github', () => ({ const androidLink = 'https://expensify.app/ANDROID_LINK'; const iOSLink = 'https://expensify.app/IOS_LINK'; const webLink = 'https://expensify.app/WEB_LINK'; +const desktopLink = 'https://expensify.app/DESKTOP_LINK'; const androidQRCode = `![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${androidLink})`; +const desktopQRCode = `![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${desktopLink})`; const iOSQRCode = `![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${iOSLink})`; const webQRCode = `![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${webLink})`; -const message = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! :test_tube::test_tube: +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: Built from App PR Expensify/App#12 Mobile-Expensify PR Expensify/Mobile-Expensify#13. | Android :robot: | iOS :apple: | | ------------- | ------------- | | ${androidLink} | ${iOSLink} | | ${androidQRCode} | ${iOSQRCode} | -| Web :spider_web: | -| ------------- | -| ${webLink} | -| ${webQRCode} | +| Desktop :computer: | Web :spider_web: | +| ------------- | ------------- | +| ${desktopLink} | ${webLink} | +| ${desktopQRCode} | ${webQRCode} | --- :eyes: [View the workflow run that generated this build](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/1234) :eyes: `; -const onlyAppMessage = `:test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! :test_tube::test_tube: +const onlyAppMessage = `: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: Built from App PR Expensify/App#12. | Android :robot: | iOS :apple: | | ------------- | ------------- | | ${androidLink} | ā© SKIPPED ā© | | ${androidQRCode} | The build for iOS was skipped | -| Web :spider_web: | -| ------------- | -| ā© SKIPPED ā© | -| The build for Web was skipped | +| Desktop :computer: | Web :spider_web: | +| ------------- | ------------- | +| āŒ FAILED āŒ | ā© SKIPPED ā© | +| The QR code can't be generated, because the Desktop build failed | The build for Web was skipped | --- @@ -92,10 +94,10 @@ Built from Mobile-Expensify PR Expensify/Mobile-Expensify#13. | ${androidLink} | ${iOSLink} | | ${androidQRCode} | ${iOSQRCode} | -| Web :spider_web: | -| ------------- | -| ā© SKIPPED ā© | -| The build for Web was skipped | +| Desktop :computer: | Web :spider_web: | +| ------------- | ------------- | +| ā© SKIPPED ā© | ā© SKIPPED ā© | +| The build for Desktop was skipped | The build for Web was skipped | --- @@ -117,14 +119,16 @@ describe('Post test build comments action tests', () => { 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(androidLink); when(core.getInput).calledWith('IOS_LINK').mockReturnValue(iOSLink); when(core.getInput).calledWith('WEB_LINK').mockReturnValue('https://expensify.app/WEB_LINK'); + when(core.getInput).calledWith('DESKTOP_LINK').mockReturnValue('https://expensify.app/DESKTOP_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, and Web. Happy testing!', + 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', }, @@ -152,12 +156,13 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue('skipped'); when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue('skipped'); + when(core.getInput).calledWith('DESKTOP', {required: false}).mockReturnValue('failure'); 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, and Web. Happy testing!', + 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', }, @@ -187,6 +192,7 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('ANDROID_LINK').mockReturnValue(androidLink); when(core.getInput).calledWith('IOS_LINK').mockReturnValue(iOSLink); when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue('skipped'); + when(core.getInput).calledWith('DESKTOP', {required: false}).mockReturnValue('skipped'); createCommentMock.mockResolvedValue({} as CreateCommentResponse); mockListComments.mockResolvedValue({ data: [ diff --git a/tsconfig.json b/tsconfig.json index f8058fb17f35..7e1dba141d7c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ }, "include": [ "src", + "desktop", "web", "website", "docs",