diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0f59295a3463..d58a81c8d80a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -163,46 +163,25 @@ jobs: name: Build and deploy Android HybridApp needs: prep runs-on: ubuntu-latest-xl - defaults: - run: - working-directory: Mobile-Expensify/react-native steps: - - name: Checkout App repo - uses: actions/checkout@v4 - - - name: Checkout Mobile-Expensify repo + - name: Checkout App and Mobile-Expensify repo 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 + - name: Update submodule to match main run: | - git submodule update --init - # Update submodule to latest on staging - git fetch - git checkout staging + git submodule update --init --remote - 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: Install node modules - run: | - npm install - cd .. && npm install - - # Fixes https://github.com/Expensify/App/issues/51682 - npm run grunt:build:shared + - name: Setup Node + id: setup-node + uses: ./.github/actions/composite/setupNode - name: Setup Java uses: actions/setup-java@v4 @@ -214,7 +193,6 @@ jobs: 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 @@ -229,7 +207,7 @@ jobs: 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 + cp ./upload-key.keystore Mobile-Expensify/Android - name: Load Android upload keystore credentials from 1Password id: load-credentials @@ -496,47 +474,31 @@ jobs: runs-on: macos-13-xlarge env: DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer - 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 - # Update submodule to latest on staging - git fetch - git checkout staging + git submodule update --init --remote - name: Configure MapBox SDK run: | ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - - uses: actions/setup-node@v4 + - name: Setup Node id: setup-node - with: - node-version-file: 'Mobile-Expensify/react-native/.nvmrc' - cache-dependency-path: 'Mobile-Expensify/react-native' - - - name: Install node modules - run: | - npm install - cd .. && npm install + uses: ./.github/actions/composite/setupNode - 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 @@ -545,12 +507,12 @@ jobs: uses: actions/cache@v4 id: pods-cache with: - path: ios/Pods - key: ${{ runner.os }}-pods-cache-${{ hashFiles('ios/Podfile.lock', 'firebase.json') }} + path: Mobile-Expensify/ios/Pods + key: ${{ runner.os }}-pods-cache-${{ hashFiles('Mobile-Expensify/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" + run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('Mobile-Expensify/ios/Podfile.lock') == hashFiles('Mobile-Expensify/ios/Pods/Manifest.lock') }}" >> "$GITHUB_OUTPUT" - name: Install cocoapods uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 @@ -558,7 +520,7 @@ jobs: with: timeout_minutes: 10 max_attempts: 5 - command: cd Mobile-Expensify/iOS && pod install + command: npm run pod-install - name: Install 1Password CLI uses: 1password/install-cli-action@v1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..7b3a3d9f9432 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Mobile-Expensify"] + path = Mobile-Expensify + url = https://github.com/Expensify/Mobile-Expensify.git diff --git a/Mobile-Expensify b/Mobile-Expensify new file mode 160000 index 000000000000..7df7a0a1002d --- /dev/null +++ b/Mobile-Expensify @@ -0,0 +1 @@ +Subproject commit 7df7a0a1002d7622fd8b9c59a5dbfcc39164e736 diff --git a/README.md b/README.md index 9f73a0012bef..77b9d509a74d 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,102 @@ export default withOnyx({ 1. The application uses [`react-navigation`](https://reactnavigation.org/) for navigating between parts of the app. 1. [Higher Order Components](https://reactjs.org/docs/higher-order-components.html) are used to connect React components to persistent storage via [`react-native-onyx`](https://github.com/Expensify/react-native-onyx). +---- +# HybridApp + +Currently, the production Expensify app contains both "Expensify Classic" and "New Expensify". The file structure is as follows: + +- 📂 [**App**](https://github.com/Expensify/App) + - 📂 [**android**](https://github.com/Expensify/App/tree/main/android): New Expensify Android specific code (not a part of HybridApp native code) + - 📂 [**ios**](https://github.com/Expensify/App/tree/main/ios): New Expensify iOS specific code (not a part of HybridApp native code) + - 📂 [**src**](https://github.com/Expensify/App/tree/main/src): New Expensify TypeScript logic + - 📂 [**Mobile-Expensify**](https://github.com/Expensify/Mobile-Expensify): `git` submodule that is pointed to [Mobile-Expensify](https://github.com/Expensify/Mobile-Expensify) + - 📂 [**Android**](https://github.com/Expensify/Mobile-Expensify/tree/main/Android): Expensify Classic Android specific code + - 📂 [**iOS**](https://github.com/Expensify/Mobile-Expensify/tree/main/iOS): Expensify Classic iOS specific code + - 📂 [**app**](https://github.com/Expensify/Mobile-Expensify/tree/main/app): Expensify Classic JavaScript logic (aka YAPL) + +You can only build HybridApp if you have been granted access to [`Mobile-Expensify`](https://github.com/Expensify/Mobile-Expensify). For most contributors, you will be working on the standalone NewDot application. + +## Getting started with HybridApp + +1. If you haven't, please follow [these instructions](https://github.com/Expensify/App?tab=readme-ov-file#getting-started) to setup the NewDot local environment. +2. Run `git submodule update --init` to download the `Mobile-Expensify` sourcecode. +- If you have access to `Mobile-Expensify` and the command fails with a https-related error add this to your `~/.gitconfig` file: + + ``` + [url "ssh://git@github.com/"] + insteadOf = https://github.com/ + ``` + +At this point, the default behavior of some `npm` scripts will change to target HybridApp: +- `npm run android` - build HybridApp for Android +- `npm run ios` - build HybridApp for iOS +- `npm run ipad` - build HybridApp for iPad +- `npm run ipad-sm` - build HybridApp for small iPad +- `npm run pod-install` - install pods for HybridApp +- `npm run clean` - clean native code of HybridApp + +If for some reason, you need to target the standalone NewDot application, you can append `*-standalone` to each of these scripts (eg. `npm run ios-standalone` will build NewDot instead of HybridApp). + +## Working with HybridApp +Day-to-day work with HybridApp shouldn't differ much from the work on the standalone NewDot repo. + +The main difference is that the native code which runs React Native is located in `./Mobile-Expensify/Android` and `./Mobile-Expensify/iOS` directories. It means, that changes in `./android` and `./ios` folders in the root **won't affect the HybridApp build**. + +In that case, if you'd like to eg. remove `Pods`, you need to do it in `./Mobile-Expensify/iOS`. The same rule applies to Android builds - if you'd like to delete `.cxx`, `build` or `.gradle` directories, you need to go to `./Mobile-Expensify/android` first. + +Additionally, If you'd like to open the HybridApp project in Android Studio or XCode, you **must choose a workspace located in the `Mobile-Expensify`** directory: + +- Android: `./Mobile-Expensify/Android` +- iOS: `./Mobile-Expensify/iOS/Expensify.xcworkspace` + +### Updating the Mobile-Expensify submodule + +`Mobile-Expensify` directory is a git submodule. It means, that it points to a specific commit on the `Mobile-Expensify` repository. If you'd like to download the most recent changes from `main`, please use the following command: + +`git submodule update --remote` + +### Modifying Mobile-Expensify code + +It's important to emphasise that a git submodule is just a **regular git repository** after all. It means that you can switch branches, pull the newest changes, and execute all regular git commands within the `Mobile-Expensify` directory. + +> [!Note] +> #### For external contributors +> +> If you'd like to modify the `Mobile-Expensify` source code, it is best that you create your own fork. Then, you can swap origin of the remote repository by executing this command: +> +> `cd Mobile-Expensify && git remote set-url origin ` +> +> This way, you'll attach the submodule to your fork repository. + +### Adding HybridApp-related patches + +Applying patches from the `patches` directory is performed automatically with the `npm install` command executed in `Expensify/App`. + +If you'd like to add HybridApp-specific patches, use the `--patch-dir` flag: + +`npx patch-package --patch-dir Mobile-Expensify/patches` + +### HybridApp troubleshooting + +#### Cleaning the repo +- `npm run clean` - deep clean of all HybridApp artifacts (including NewDot's `node_modules`) +- `npm run clean -- --ios` - clean only iOS HybridApp artifacts (`Pods`, `build` folder, `DerivedData`) +- `npm run clean -- --android` - clean only Android HybridApp artifacts (`.cxx`, `build`, and `.gradle` folders, execute `./gradlew clean`) + +If you'd like to do it manually, remember to `cd Mobile-Expensify` first! + +#### Common errors +1. **Please check your internet connection** - set `_isOnDev` in `api.js` to always return `false` +2. **CDN: trunk URL couldn't be downloaded** - `cd Mobile-Expensify/iOS && pod repo remove trunk` + +3. **Task :validateSigningRelease FAILED** - open `Mobile-Expensify/Android/build.gradle` and do the following: + ``` + - signingConfig signingConfigs.release + + signingConfig signingConfigs.debug + ``` +4. **Build service could not create build operation: unknown error while handling message: MsgHandlingError(message: "unable to initiate PIF transfer session (operation in progress?)")** - reopen XCode + ---- # Philosophy diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8dbf67a150bd..9e0ba567ac48 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -71,9 +71,9 @@ platform :android do desc "Generate a production HybridApp AAB" lane :build_hybrid do - ENV["ENVFILE"]="../.env.production.hybridapp" + ENV["ENVFILE"]="Mobile-Expensify/.env.production.hybridapp" gradle( - project_dir: '../Android', + project_dir: 'Mobile-Expensify/Android', task: "bundleRelease", flags: "--refresh-dependencies", properties: { @@ -118,7 +118,7 @@ platform :android do lane :build_local_hybrid do ENV["ENVFILE"]=".env.production" gradle( - project_dir: '../Android', + project_dir: 'Mobile-Expensify/Android', task: 'assemble', flavor: 'Production', build_type: 'Release', @@ -372,7 +372,7 @@ platform :ios do desc "Build an iOS HybridApp production build" lane :build_hybrid do - ENV["ENVFILE"]="../.env.production.hybridapp" + ENV["ENVFILE"]="Mobile-Expensify/.env.production.hybridapp" setupIOSSigningCertificate() @@ -389,7 +389,7 @@ platform :ios do ) build_app( - workspace: "../iOS/Expensify.xcworkspace", + workspace: "Mobile-Expensify/iOS/Expensify.xcworkspace", scheme: "Expensify", output_name: "Expensify.ipa", export_method: "app-store", @@ -418,9 +418,9 @@ platform :ios do desc "Build an unsigned iOS HybridApp production build" lane :build_unsigned_hybrid do - ENV["ENVFILE"]="../Mobile-Expensify/.env.production.hybridapp" + ENV["ENVFILE"]="./Mobile-Expensify/.env.production.hybridapp" build_app( - workspace: "../Mobile-Expensify/iOS/Expensify.xcworkspace", + workspace: "./Mobile-Expensify/iOS/Expensify.xcworkspace", scheme: "Expensify" ) setIOSBuildOutputsInEnv() @@ -537,7 +537,7 @@ platform :ios do dsym_path: ENV[KEY_DSYM_PATH], gsp_path: "./ios/GoogleService-Info.plist", # Assuming we are running this from the react-native submodule directory for HybridApp - binary_path: "../iOS/Pods/FirebaseCrashlytics/upload-symbols" + binary_path: "./Mobile-Expensify/iOS/Pods/FirebaseCrashlytics/upload-symbols" ) end diff --git a/ios/Podfile b/ios/Podfile index 4d139711ef01..41dc5179752d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -3,6 +3,7 @@ require File.join(File.dirname(`node --print "require.resolve('expo/package.json # This value is used by $RNMapboxMaps $RNMapboxMapsImpl = 'mapbox' $VCDisableFrameProcessors = true +ENV['PROJECT_ROOT_PATH'] = "./"; def node_require(script) # Resolve script with node to allow for hoisting @@ -82,6 +83,7 @@ target 'NewExpensify' do # ENV Variable enables/disables TurboModules ENV['RCT_NEW_ARCH_ENABLED'] = '1'; + use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c8e92768eb9a..18eba3d79c27 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3290,6 +3290,6 @@ SPEC CHECKSUMS: VisionCamera: c95a8ad535f527562be1fb05fb2fd324578e769c Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 -PODFILE CHECKSUM: 15e2f095b9c80d658459723edf84005a6867debf +PODFILE CHECKSUM: 615266329434ea4a994dccf622008a2197313c88 COCOAPODS: 1.15.2 diff --git a/package.json b/package.json index 3c6ca5e63a43..4cd6fc679f37 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,18 @@ "setupNewDotWebForEmulators": "./scripts/setup-newdot-web-emulators.sh", "startAndroidEmulator": "./scripts/start-android.sh", "postinstall": "./scripts/postInstall.sh", - "clean": "npx react-native clean-project-auto", - "android": "./scripts/set-pusher-suffix.sh && npx react-native run-android --mode=developmentDebug --appId=com.expensify.chat.dev --active-arch-only", - "ios": "./scripts/set-pusher-suffix.sh && npx react-native run-ios --list-devices --mode=\"DebugDevelopment\" --scheme=\"New Expensify Dev\"", + "clean": "./scripts/clean.sh", + "clean-standalone": "./scripts/clean.sh --new-dot", + "android": "./scripts/set-pusher-suffix.sh && ./scripts/run-build.sh --android", + "android-standalone": "./scripts/set-pusher-suffix.sh && ./scripts/run-build.sh --android --new-dot", + "ios": "./scripts/set-pusher-suffix.sh && ./scripts/run-build.sh --ios", + "ios-standalone": "./scripts/set-pusher-suffix.sh && ./scripts/run-build.sh --ios --new-dot", "pod-install": "./scripts/pod-install.sh", - "ipad": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (12.9-inch) (6th generation)\\\" --mode=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", - "ipad-sm": "concurrently \"npx react-native run-ios --simulator=\\\"iPad Pro (11-inch) (4th generation)\\\" --mode=\\\"DebugDevelopment\\\" --scheme=\\\"New Expensify Dev\\\"\"", + "pod-install-standalone": "./scripts/pod-install.sh --new-dot", + "ipad": "concurrently \"./scripts/run-build.sh --ipad\"", + "ipad-standalone": "concurrently \"./scripts/run-build.sh --ipad --new-dot\"", + "ipad-sm": "concurrently \"./scripts/run-build.sh --ipad-sm\"", + "ipad-sm-standalone": "concurrently \"./scripts/run-build.sh --ipad-sm --new-dot\"", "start": "npx react-native start", "web": "./scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server", "web-proxy": "ts-node web/proxy.ts", diff --git a/patches/@onfido+react-native-sdk+10.6.0.patch b/patches/@onfido+react-native-sdk+10.6.0.patch index 201e9ab92c22..87f0aad1618d 100644 --- a/patches/@onfido+react-native-sdk+10.6.0.patch +++ b/patches/@onfido+react-native-sdk+10.6.0.patch @@ -1252,7 +1252,7 @@ index a9de0d0..da83d9f 100644 - s.dependency "Onfido", "~> 29.6.0" + s.dependency "Onfido", "~> 29.7.0" + -+ if ENV['USE_FRAMEWORKS'] == '1' ++ if ENV['USE_FRAMEWORKS'] != nil + s.pod_target_xcconfig = { + "OTHER_CFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", diff --git a/patches/@react-native+gradle-plugin+0.75.2.patch b/patches/@react-native+gradle-plugin+0.75.2+001+initial.patch similarity index 100% rename from patches/@react-native+gradle-plugin+0.75.2.patch rename to patches/@react-native+gradle-plugin+0.75.2+001+initial.patch diff --git a/patches/@react-native-camera-roll+camera-roll+7.4.0+001+hybrid-app.patch b/patches/@react-native-camera-roll+camera-roll+7.4.0+001+hybrid-app.patch deleted file mode 100644 index 9d848520a943..000000000000 --- a/patches/@react-native-camera-roll+camera-roll+7.4.0+001+hybrid-app.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle b/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle -index 6891fa3..8397f95 100644 ---- a/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle -+++ b/node_modules/@react-native-camera-roll/camera-roll/android/build.gradle -@@ -81,7 +81,9 @@ def findNodeModulePath(baseDir, packageName) { - } - - def resolveReactNativeDirectory() { -- def reactNative = file("${findNodeModulePath(rootProject.projectDir, "react-native")}") -+ def projectDir = this.hasProperty('reactNativeProject') ? this.reactNativeProject : rootProject.projectDir -+ def modulePath = file(projectDir); -+ def reactNative = file("${findNodeModulePath(modulePath, 'react-native')}") - if (reactNative.exists()) { - return reactNative - } diff --git a/patches/@react-native-community+cli-platform-android+14.0.0+001+hybrid-app.patch b/patches/@react-native-community+cli-platform-android+14.0.0+001+hybrid-app.patch deleted file mode 100644 index 7f64391efe4c..000000000000 --- a/patches/@react-native-community+cli-platform-android+14.0.0+001+hybrid-app.patch +++ /dev/null @@ -1,52 +0,0 @@ -diff --git a/node_modules/@react-native-community/cli-platform-android/native_modules.gradle b/node_modules/@react-native-community/cli-platform-android/native_modules.gradle -index 43296c6..0d91033 100644 ---- a/node_modules/@react-native-community/cli-platform-android/native_modules.gradle -+++ b/node_modules/@react-native-community/cli-platform-android/native_modules.gradle -@@ -149,16 +149,18 @@ class ReactNativeModules { - private ProviderFactory providers - private String packageName - private File root -+ private File rnRoot - private ArrayList> reactNativeModules - private HashMap reactNativeModulesBuildVariants - private String reactNativeVersion - - private static String LOG_PREFIX = ":ReactNative:" - -- ReactNativeModules(Logger logger, ProviderFactory providers, File root) { -+ ReactNativeModules(Logger logger, ProviderFactory providers, File root, File rnRoot) { - this.logger = logger - this.providers = providers - this.root = root -+ this.rnRoot = rnRoot - - def (nativeModules, reactNativeModulesBuildVariants, androidProject, reactNativeVersion) = this.getReactNativeConfig() - this.reactNativeModules = nativeModules -@@ -440,10 +442,10 @@ class ReactNativeModules { - */ - def cliResolveScript = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}" - String[] nodeCommand = ["node", "-e", cliResolveScript] -- def cliPath = this.getCommandOutput(nodeCommand, this.root) -+ def cliPath = this.getCommandOutput(nodeCommand, this.rnRoot) - - String[] reactNativeConfigCommand = ["node", cliPath, "config", "--platform", "android"] -- def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.root) -+ def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.rnRoot) - - def json - try { -@@ -513,7 +515,13 @@ class ReactNativeModules { - */ - def projectRoot = rootProject.projectDir - --def autoModules = new ReactNativeModules(logger, providers, projectRoot) -+def autoModules -+ -+if(this.hasProperty('reactNativeProject')){ -+ autoModules = new ReactNativeModules(logger, providers, projectRoot, new File(projectRoot, reactNativeProject)) -+} else { -+ autoModules = new ReactNativeModules(logger, providers, projectRoot, projectRoot) -+} - - def reactNativeVersionRequireNewArchEnabled(autoModules) { - def rnVersion = autoModules.reactNativeVersion diff --git a/patches/@react-native-community+cli-platform-ios+14.0.0+001+hybrid-app.patch b/patches/@react-native-community+cli-platform-ios+14.0.0+001+hybrid-app.patch deleted file mode 100644 index e54ab17c43dd..000000000000 --- a/patches/@react-native-community+cli-platform-ios+14.0.0+001+hybrid-app.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff --git a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb -index 82f537c..df441e2 100644 ---- a/node_modules/@react-native-community/cli-platform-ios/native_modules.rb -+++ b/node_modules/@react-native-community/cli-platform-ios/native_modules.rb -@@ -12,7 +12,7 @@ - require 'pathname' - require 'cocoapods' - --def use_native_modules!(config = nil) -+def updateConfig(config = nil) - if (config.is_a? String) - Pod::UI.warn("Passing custom root to use_native_modules! is deprecated.", - [ -@@ -24,7 +24,6 @@ def use_native_modules!(config = nil) - # Resolving the path the RN CLI. The `@react-native-community/cli` module may not be there for certain package managers, so we fall back to resolving it through `react-native` package, that's always present in RN projects - cli_resolve_script = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}" - cli_bin = Pod::Executable.execute_command("node", ["-e", cli_resolve_script], true).strip -- - if (!config) - json = [] - -@@ -36,9 +35,24 @@ def use_native_modules!(config = nil) - - config = JSON.parse(json.join("\n")) - end -+end -+ -+def use_native_modules!(config = nil) -+ if (ENV['REACT_NATIVE_DIR']) -+ Dir.chdir(ENV['REACT_NATIVE_DIR']) do -+ config = updateConfig(config) -+ end -+ else -+ config = updateConfig(config) -+ end - - project_root = Pathname.new(config["project"]["ios"]["sourceDir"]) - -+ if(ENV["PROJECT_ROOT_DIR"]) -+ project_root = File.join(Dir.pwd, ENV["PROJECT_ROOT_DIR"]) -+ -+ end -+ - packages = config["dependencies"] - found_pods = [] - diff --git a/patches/expo+51.0.31+001+hybrid-app.patch b/patches/expo+51.0.31+001+hybrid-app.patch deleted file mode 100644 index 44048857fc1b..000000000000 --- a/patches/expo+51.0.31+001+hybrid-app.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/expo/scripts/autolinking.gradle b/node_modules/expo/scripts/autolinking.gradle -index 929b7f0..c948ebb 100644 ---- a/node_modules/expo/scripts/autolinking.gradle -+++ b/node_modules/expo/scripts/autolinking.gradle -@@ -1,6 +1,6 @@ - // Resolve `expo` > `expo-modules-autolinking` dependency chain - def autolinkingPath = ["node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })"] --apply from: new File( -+apply from: hasProperty("reactNativeProject") ? file('../../expo-modules-autolinking/scripts/android/autolinking_implementation.gradle') : new File( - providers.exec { - workingDir(rootDir) - commandLine(autolinkingPath) diff --git a/patches/expo-av+14.0.7+001+hybrid-app.patch b/patches/expo-av+14.0.7+001+hybrid-app.patch deleted file mode 100644 index 4cf0dee990c5..000000000000 --- a/patches/expo-av+14.0.7+001+hybrid-app.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/node_modules/expo-av/android/build.gradle b/node_modules/expo-av/android/build.gradle -index 11e7574..6dae6a0 100644 ---- a/node_modules/expo-av/android/build.gradle -+++ b/node_modules/expo-av/android/build.gradle -@@ -3,12 +3,13 @@ apply plugin: 'com.android.library' - group = 'host.exp.exponent' - version = '14.0.7' - -+def REACT_NATIVE_PATH = this.hasProperty('reactNativeProject') ? this.reactNativeProject + '/node_modules/react-native/package.json' : 'react-native/package.json' - def REACT_NATIVE_BUILD_FROM_SOURCE = findProject(":ReactAndroid") != null - def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE - ? findProject(":ReactAndroid").getProjectDir().parent - : file(providers.exec { - workingDir(rootDir) -- commandLine("node", "--print", "require.resolve('react-native/package.json')") -+ commandLine("node", "--print", "require.resolve('${REACT_NATIVE_PATH}')") - }.standardOutput.asText.get().trim()).parent - - def reactNativeArchitectures() { diff --git a/patches/expo-modules-autolinking+1.11.2+001+hybrid-app.patch b/patches/expo-modules-autolinking+1.11.2+001+hybrid-app.patch deleted file mode 100644 index a345f84b8f20..000000000000 --- a/patches/expo-modules-autolinking+1.11.2+001+hybrid-app.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle b/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle -index f085818..fcb9594 100644 ---- a/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle -+++ b/node_modules/expo-modules-autolinking/scripts/android/autolinking_implementation.gradle -@@ -152,12 +152,13 @@ class ExpoAutolinkingManager { - } - - static private String[] convertOptionsToCommandArgs(String command, Map options) { -+ def expoPath = options.searchPaths ? "../react-native/node_modules/expo" : "expo" - String[] args = [ - 'node', - '--no-warnings', - '--eval', - // Resolve the `expo` > `expo-modules-autolinking` chain from the project root -- 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo\')] }))(process.argv.slice(1))', -+ "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'${expoPath}\')] }))(process.argv.slice(1))", - '--', - command, - '--platform', -diff --git a/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb b/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb -index 5d46f1e..fec4f34 100644 ---- a/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb -+++ b/node_modules/expo-modules-autolinking/scripts/ios/project_integrator.rb -@@ -215,6 +215,7 @@ module Expo - args = autolinking_manager.base_command_args.map { |arg| "\"#{arg}\"" } - platform = autolinking_manager.platform_name.downcase - package_names = autolinking_manager.packages_to_generate.map { |package| "\"#{package.name}\"" } -+ expo_path = ENV['REACT_NATIVE_DIR'] ? "#{ENV['REACT_NATIVE_DIR']}/node_modules/expo" : "expo" - - <<~SUPPORT_SCRIPT - #!/usr/bin/env bash -@@ -262,7 +263,7 @@ module Expo - - with_node \\ - --no-warnings \\ -- --eval "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))" \\ -+ --eval "require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'#{expo_path}/package.json\')] }))(process.argv.slice(1))" \\ - generate-modules-provider #{args.join(' ')} \\ - --target "#{modules_provider_path}" \\ - --platform "apple" \\ diff --git a/patches/expo-modules-core+1.12.23+002+hybrid-app.patch b/patches/expo-modules-core+1.12.23+002+hybrid-app.patch deleted file mode 100644 index b32830615aaa..000000000000 --- a/patches/expo-modules-core+1.12.23+002+hybrid-app.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/node_modules/expo-modules-core/android/build.gradle b/node_modules/expo-modules-core/android/build.gradle -index f22a3c3..4884cea 100644 ---- a/node_modules/expo-modules-core/android/build.gradle -+++ b/node_modules/expo-modules-core/android/build.gradle -@@ -20,12 +20,13 @@ def isExpoModulesCoreTests = { - }.call() - - def REACT_NATIVE_BUILD_FROM_SOURCE = findProject(":packages:react-native:ReactAndroid") != null --def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE -- ? findProject(":packages:react-native:ReactAndroid").getProjectDir().parent -- : file(providers.exec { -+def FALLBACK_REACT_NATIVE_DIR = hasProperty("reactNativeProject") ? file('../../react-native') : file(providers.exec { - workingDir(rootDir) - commandLine("node", "--print", "require.resolve('react-native/package.json')") - }.standardOutput.asText.get().trim()).parent -+def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE -+ ? findProject(":packages:react-native:ReactAndroid").getProjectDir().parent -+ : FALLBACK_REACT_NATIVE_DIR - - def reactProperties = new Properties() - file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } diff --git a/patches/react-native-plaid-link-sdk+11.11.0.patch b/patches/react-native-plaid-link-sdk+11.11.0.patch index 28e492f6999f..39ae7b3cd1e7 100644 --- a/patches/react-native-plaid-link-sdk+11.11.0.patch +++ b/patches/react-native-plaid-link-sdk+11.11.0.patch @@ -23,7 +23,7 @@ index 7c60081..4a13a3c 100644 # we don't want this to be seen by Swift s.private_header_files = 'ios/PLKFabricHelpers.h' -+ if ENV['USE_FRAMEWORKS'] == '1' ++ if ENV['USE_FRAMEWORKS'] != nil + s.pod_target_xcconfig = { + "OTHER_CFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DUSE_FRAMEWORKS", diff --git a/patches/react-native-reanimated+3.16.3+001+hybrid-app.patch b/patches/react-native-reanimated+3.16.3+001+hybrid-app.patch deleted file mode 100644 index 835df1f034a9..000000000000 --- a/patches/react-native-reanimated+3.16.3+001+hybrid-app.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb -index 9fc7b15..e453d84 100644 ---- a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb -+++ b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb -@@ -17,7 +17,11 @@ def find_config() - :react_native_reanimated_dir_from_pods_root => nil, - } - -- react_native_node_modules_dir = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native/package.json')"`), '..') -+ root_project = Pod::Config.instance.installation_root.to_s -+ if(ENV['PROJECT_ROOT_DIR']) -+ root_project = ENV['PROJECT_ROOT_DIR'] -+ end -+ react_native_node_modules_dir = File.join(File.dirname(`cd "#{root_project}" && node --print "require.resolve('react-native/package.json')"`), '..') - react_native_json = try_to_parse_react_native_package_json(react_native_node_modules_dir) - - if react_native_json == nil diff --git a/react-native.config.js b/react-native.config.js index 6d6dd3f5805f..a8c2436688e4 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -1,7 +1,7 @@ module.exports = { project: { - ios: {sourceDir: 'ios'}, - android: {}, + ios: {sourceDir: process.env.PROJECT_ROOT_PATH + 'ios'}, + android: {sourceDir: process.env.PROJECT_ROOT_PATH + 'android'}, }, assets: ['./assets/fonts/native'], }; diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh index 4ce023755258..29e121acc968 100755 --- a/scripts/applyPatches.sh +++ b/scripts/applyPatches.sh @@ -9,9 +9,15 @@ source "$SCRIPTS_DIR/shellUtils.sh" # Wrapper to run patch-package. function patchPackage { + # See if we're in the HybridApp repo + IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) + OS="$(uname)" if [[ "$OS" == "Darwin" || "$OS" == "Linux" ]]; then npx patch-package --error-on-fail --color=always + if [[ "$IS_HYBRID_APP_REPO" == "true" ]]; then + npx patch-package --patch-dir 'Mobile-Expensify/patches' --error-on-fail --color=always + fi else error "Unsupported OS: $OS" exit 1 diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 000000000000..1ecd73731b61 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +BLUE='\033[1;34m' +NC='\033[0m' + +# See if we're in the HybridApp repo +IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) + +if [[ "$IS_HYBRID_APP_REPO" == "true" && "$1" != "--new-dot" ]]; then + echo -e "${BLUE}Cleaning HybridApp project...${NC}" + # Navigate to Mobile-Expensify repository, and clean + cd Mobile-Expensify + npm run clean -- "$@" +else + # Clean NewDot + echo -e "${BLUE}Cleaning standalone NewDot project...${NC}" + npx react-native clean-project-auto +fi diff --git a/scripts/is-hybrid-app.sh b/scripts/is-hybrid-app.sh new file mode 100755 index 000000000000..32ca190ac832 --- /dev/null +++ b/scripts/is-hybrid-app.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +if [[ ! -d Mobile-Expensify ]]; then + echo false + exit 0 +else + cd Mobile-Expensify +fi + +# Check if 'package.json' exists +if [[ -f package.json ]]; then + # Read the 'name' field from 'package.json' + package_name=$(jq -r '.name' package.json 2>/dev/null) + + # Check if the 'name' field is 'mobile-expensify' + if [[ "$package_name" == "mobile-expensify" ]]; then + echo true + exit 0 + fi +else + echo "package.json not found in Mobile-Expensify" + echo false +fi diff --git a/scripts/pod-install.sh b/scripts/pod-install.sh index cb2976d64587..8e38f1706d6f 100755 --- a/scripts/pod-install.sh +++ b/scripts/pod-install.sh @@ -8,6 +8,9 @@ # Exit immediately if any command exits with a non-zero status set -e +BLUE='\033[1;34m' +NC='\033[0m' + # Go to project root START_DIR="$(pwd)" ROOT_DIR="$(dirname "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)")" @@ -40,6 +43,25 @@ if ! yq --version > /dev/null 2>&1; then cleanupAndExit 1 fi +# See if we're in the HybridApp repo +IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) +NEW_DOT_FLAG="false" + +if [ "$1" == "--new-dot" ]; then + NEW_DOT_FLAG="true" +fi + +if [[ "$IS_HYBRID_APP_REPO" == "true" && "$NEW_DOT_FLAG" == "false" ]]; then + echo -e "${BLUE}Executing npm run pod-install for HybridApp...${NC}" + # Navigate to the OldDot repository, and run bundle install and pod install + cd Mobile-Expensify/ios + bundle install + bundle exec pod install + exit 0 +fi + +echo -e "${BLUE}Executing npm run pod-install for standalone NewDot...${NC}" + CACHED_PODSPEC_DIR='ios/Pods/Local Podspecs' if [ -d "$CACHED_PODSPEC_DIR" ]; then info "Verifying pods from Podfile.lock match local podspecs..." diff --git a/scripts/postInstall.sh b/scripts/postInstall.sh index 782c8ef5822c..db24f04f8a6c 100755 --- a/scripts/postInstall.sh +++ b/scripts/postInstall.sh @@ -7,6 +7,16 @@ set -e ROOT_DIR=$(dirname "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)") cd "$ROOT_DIR" || exit 1 +# See if we're in the HybridApp repo +IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) + +if [[ "$IS_HYBRID_APP_REPO" == "true" ]]; then + cd Mobile-Expensify || exit 1 + npm i + + cd "$ROOT_DIR" || exit 1 +fi + # Apply packages using patch-package scripts/applyPatches.sh diff --git a/scripts/run-build.sh b/scripts/run-build.sh new file mode 100755 index 000000000000..7689aabbbf59 --- /dev/null +++ b/scripts/run-build.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +export PROJECT_ROOT_PATH + +BUILD="$1" +NEW_DOT_FLAG="false" +IOS_MODE="DebugDevelopment" +ANDROID_MODE="developmentDebug" +SCHEME="New Expensify Dev" +APP_ID="com.expensify.chat.dev" + +GREEN='\033[1;32m' +RED='\033[1;31m' +NC='\033[0m' + +# Function to print error message and exit +function print_error_and_exit { + echo -e "${RED}Error: Invalid invocation. Please use one of: [ios, ipad, ipad-sm, android].${NC}" + exit 1 +} + +# Assign the arguments to variables +if [ "$#" -eq 1 ]; then + BUILD="$1" +elif [ "$#" -eq 2 ]; then + if [ "$1" == "--new-dot" ]; then + BUILD="$2" + NEW_DOT_FLAG="true" + elif [ "$2" == "--new-dot" ]; then + BUILD="$1" + NEW_DOT_FLAG="true" + else + print_error_and_exit + fi +else + print_error_and_exit +fi + +# See if we're in the HybridApp repo +IS_HYBRID_APP_REPO=$(scripts/is-hybrid-app.sh) + + if [[ "$IS_HYBRID_APP_REPO" == "true" && "$NEW_DOT_FLAG" == "false" ]]; then + # Set HybridApp-specific arguments + IOS_MODE="Debug" + ANDROID_MODE="Debug" + SCHEME="Expensify" + APP_ID="org.me.mobiexpensifyg" + + echo -e "\n${GREEN}Starting a HybridApp build!${NC}" + PROJECT_ROOT_PATH="Mobile-Expensify/" + export CUSTOM_APK_NAME="Expensify-debug.apk" +else + echo -e "\n${GREEN}Starting a standalone NewDot build!${NC}" + echo $ANDROID_MODE + PROJECT_ROOT_PATH="./" + unset CUSTOM_APK_NAME +fi + +# Check if the argument is one of the desired values +case "$BUILD" in + --ios) + npx react-native run-ios --list-devices --mode $IOS_MODE --scheme "$SCHEME" + ;; + --ipad) + npx react-native run-ios --simulator "iPad Pro (12.9-inch) (6th generation)" --mode $IOS_MODE --scheme "$SCHEME" + ;; + --ipad-sm) + npx react-native run-ios --simulator "iPad Pro (11-inch) (4th generation)" --mode $IOS_MODE --scheme "$SCHEME" + ;; + --android) + npx react-native run-android --mode $ANDROID_MODE --appId $APP_ID --active-arch-only + ;; + *) + print_error_and_exit + ;; +esac