diff --git a/.github/actions/javascript/checkAndroidStatus/action.yml b/.github/actions/javascript/checkAndroidStatus/action.yml new file mode 100644 index 000000000000..597a6b482c9c --- /dev/null +++ b/.github/actions/javascript/checkAndroidStatus/action.yml @@ -0,0 +1,17 @@ +name: 'Check Android Status' +description: 'Checks the status of the Android track and calculates the rollout percentage.' +inputs: + GOOGLE_KEY_FILE: + description: Authentication file for Google Cloud API + required: true + PACKAGE_NAME: + description: Package name to check the status of + required: true +outputs: + HALTED: + description: True if the app is halted, false otherwise + ROLLOUT_PERCENTAGE: + description: The calculated rollout percentage +runs: + using: 'node20' + main: './index.js' diff --git a/.github/actions/javascript/checkAndroidStatus/checkAndroidStatus.ts b/.github/actions/javascript/checkAndroidStatus/checkAndroidStatus.ts new file mode 100644 index 000000000000..2869d43c57d8 --- /dev/null +++ b/.github/actions/javascript/checkAndroidStatus/checkAndroidStatus.ts @@ -0,0 +1,85 @@ +import { google } from 'googleapis'; +import GithubUtils from "@github/libs/GithubUtils"; +import * as core from '@actions/core'; + +const PACKAGE_NAME = process.env.PACKAGE_NAME; +const GOOGLE_KEY_FILE = process.env.GOOGLE_KEY_FILE; +const REPO_OWNER = process.env.REPO_OWNER || ''; +const REPO_NAME = process.env.REPO_NAME || ''; +const HALTED_STATUS = 'halted'; + +async function checkAndroidStatus() { + const auth = new google.auth.GoogleAuth({ + keyFile: GOOGLE_KEY_FILE, + scopes: ['https://www.googleapis.com/auth/androidpublisher'] + }); + + const androidApi = google.androidpublisher({ + version: 'v3', + auth: auth + }); + + try { + // Insert an edit to get an edit ID + const editResponse = await androidApi.edits.insert({ + packageName: PACKAGE_NAME, + }); + const editId = editResponse.data.id; + + // Get the production track status + const trackResponse = await androidApi.edits.tracks.get({ + packageName: PACKAGE_NAME, + editId: editId!, + track: 'production', + }); + + const status = trackResponse.data.releases?.[0]?.status || 'undefined'; + console.log('Track status:', status); + + // Check if the status is halted + const HALTED = status === HALTED_STATUS; + core.setOutput('HALTED', HALTED); + } catch (error) { + console.error('Error checking track status:', error); + process.exit(1); + } +} + +async function getLatestReleaseDate() { + const { data } = await GithubUtils.octokit.repos.getLatestRelease({ + owner: REPO_OWNER, + repo: REPO_NAME, + }); + + const releaseDate = data.published_at?.split('T')[0]; + if (!releaseDate) { + throw new Error('Unable to retrieve the latest release date from GitHub'); + } + + console.log('Latest release date:', releaseDate); + return releaseDate; +} + +function calculateRolloutPercentage(releaseDate: string): number { + const release = new Date(releaseDate); + const current = new Date(); + const daysSinceRelease = Math.floor((current.getTime() - release.getTime()) / (1000 * 60 * 60 * 24)); + console.log('Days since release:', daysSinceRelease); + + if (daysSinceRelease <= 0) return 0; + if (daysSinceRelease === 1) return 1; + if (daysSinceRelease === 2) return 2; + if (daysSinceRelease === 3) return 5; + if (daysSinceRelease === 4) return 10; + if (daysSinceRelease === 5) return 20; + if (daysSinceRelease === 6) return 50; + return 100; +} + +checkAndroidStatus() + .then(getLatestReleaseDate) + .then((releaseDate) => { + const rolloutPercentage = calculateRolloutPercentage(releaseDate); + console.log('Rollout percentage:', rolloutPercentage); + core.setOutput('ROLLOUT_PERCENTAGE', rolloutPercentage); +}); diff --git a/.github/workflows/androidBump.yml b/.github/workflows/androidBump.yml new file mode 100644 index 000000000000..4200a029723b --- /dev/null +++ b/.github/workflows/androidBump.yml @@ -0,0 +1,34 @@ +name: Android Rollout Bumper + +on: +# schedule: +# - cron: '0 0 * * *' # Runs at midnight every day + push: + branches: + - andrew-android-bump + +jobs: + android_bump: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Decrypt json Google Play credentials + run: gpg --batch --yes --decrypt --passphrase="${{ secrets.LARGE_SECRET_PASSPHRASE }}" --output android-fastlane-json-key.json android-fastlane-json-key.json.gpg + working-directory: android/app + + - name: Generate version + id: checkAndroidStatus + uses: ./.github/actions/javascript/checkAndroidStatus + with: + GOOGLE_KEY_FILE: android/app/android-fastlane-json-key.json + PACKAGE_NAME: org.me.mobiexpensifyg + + - name: Update Rollout Percentage with Fastlane + run: | + echo "HALTED: ${{ steps.checkAndroidStatus.outputs.HALTED }}" + echo "ROLLOUT_PERCENTAGE: ${{ steps.checkAndroidStatus.outputs.ROLLOUT_PERCENTAGE }}" diff --git a/package-lock.json b/package-lock.json index d0dddd52d156..6c732733981f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,6 +230,7 @@ "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", + "googleapis": "^144.0.0", "html-webpack-plugin": "^5.5.0", "http-server": "^14.1.1", "jest": "29.4.1", @@ -19046,6 +19047,16 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bin-links": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", @@ -19336,6 +19347,13 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/buffer-fill": { "version": "1.0.0", "license": "MIT" @@ -22096,6 +22114,16 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT" @@ -24612,6 +24640,13 @@ ], "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, "node_modules/extend-shallow": { "version": "3.0.2", "license": "MIT", @@ -25439,6 +25474,99 @@ "node": ">=10" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -25784,6 +25912,70 @@ "delegate": "^3.1.2" } }, + "node_modules/google-auth-library": { + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.2.tgz", + "integrity": "sha512-R+FRIfk1GBo3RdlRYWPdwk8nmtVUOn6+BkDomAC46KoU8kzXzE1HLmOasSCbWUByMMAGkknVF0G5kQ69Vj7dlA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "license": "MIT", @@ -25854,6 +26046,20 @@ "version": "1.1.0", "license": "ISC" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -30013,6 +30219,16 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -30132,6 +30348,29 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kdbush": { "version": "4.0.2", "license": "ISC" @@ -32157,22 +32396,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-forge": { "version": "1.3.1", "license": "(BSD-3-Clause OR GPL-2.0)", @@ -39736,6 +39959,12 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/traverse": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", @@ -40500,6 +40729,13 @@ "requires-port": "^1.0.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "dev": true, + "license": "BSD" + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "dev": true, @@ -41487,6 +41723,16 @@ "version": "3.6.2", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "license": "MIT", @@ -41506,6 +41752,12 @@ "node": ">=8" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", diff --git a/package.json b/package.json index 61e84e10e4c5..d0ea453592e1 100644 --- a/package.json +++ b/package.json @@ -287,6 +287,7 @@ "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.2.2", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", + "googleapis": "^144.0.0", "html-webpack-plugin": "^5.5.0", "http-server": "^14.1.1", "jest": "29.4.1",