diff --git a/autolink/fixtures/rn68/MainActivity.java.template b/autolink/fixtures/rn68/MainActivity.java.template deleted file mode 100644 index 8502b9e3ffd..00000000000 --- a/autolink/fixtures/rn68/MainActivity.java.template +++ /dev/null @@ -1,40 +0,0 @@ -package com.app; - -import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "app"; - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and - * you can specify the rendered you wish to use (Fabric or the older renderer). - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new MainActivityDelegate(this, getMainComponentName()); - } - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(ReactActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - } -} diff --git a/autolink/fixtures/rn69/MainActivity.java.template b/autolink/fixtures/rn69/MainActivity.java.template deleted file mode 100644 index 02d2cd8d20c..00000000000 --- a/autolink/fixtures/rn69/MainActivity.java.template +++ /dev/null @@ -1,48 +0,0 @@ -package com.app; - -import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "app"; - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and - * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer - * (Paper). - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new MainActivityDelegate(this, getMainComponentName()); - } - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(ReactActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - - @Override - protected boolean isConcurrentRootEnabled() { - // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). - // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - } -} diff --git a/autolink/fixtures/rn71/MainActivity.java.template b/autolink/fixtures/rn71/MainActivity.java.template deleted file mode 100644 index 52b2a7c46b8..00000000000 --- a/autolink/fixtures/rn71/MainActivity.java.template +++ /dev/null @@ -1,35 +0,0 @@ -package com.app; - -import com.reactnativenavigation.NavigationActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactActivityDelegate; - -public class MainActivity extends NavigationActivity { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "app"; - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link - * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React - * (aka React 18) with two boolean flags. - */ - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new DefaultReactActivityDelegate( - this, - getMainComponentName(), - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled - // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18). - DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled - ); - } -} diff --git a/autolink/fixtures/rn77/MainActivity.kt.template b/autolink/fixtures/rn77/MainActivity.kt.template new file mode 100644 index 00000000000..853728d56a1 --- /dev/null +++ b/autolink/fixtures/rn77/MainActivity.kt.template @@ -0,0 +1,22 @@ +package com.app + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "rn770" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/autolink/fixtures/rn77/MainApplication.kt.template b/autolink/fixtures/rn77/MainApplication.kt.template new file mode 100644 index 00000000000..9e7449efca7 --- /dev/null +++ b/autolink/fixtures/rn77/MainApplication.kt.template @@ -0,0 +1,44 @@ +package com.app + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, OpenSourceMergedSoMapping) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + } +} diff --git a/autolink/fixtures/rn77/build.gradle.template b/autolink/fixtures/rn77/build.gradle.template new file mode 100644 index 00000000000..9766946918e --- /dev/null +++ b/autolink/fixtures/rn77/build.gradle.template @@ -0,0 +1,21 @@ +buildscript { + ext { + buildToolsVersion = "35.0.0" + minSdkVersion = 24 + compileSdkVersion = 35 + targetSdkVersion = 35 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.0.21" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +apply plugin: "com.facebook.react.rootproject" diff --git a/autolink/postlink/__helpers__/fixtures.js b/autolink/postlink/__helpers__/fixtures.js new file mode 100644 index 00000000000..db8f05be2ff --- /dev/null +++ b/autolink/postlink/__helpers__/fixtures.js @@ -0,0 +1,24 @@ +const prepareFixtureDuplicate = ({ rnVersion, userFixtureFileName, patchedFixtureFileName }) => { + const fs = require('node:fs'); + const path = require('node:path'); + const os = require('node:os'); + + const userFixtureRelPath = _getRelativeFixturePath(rnVersion, userFixtureFileName); + + const userFixturePath = path.resolve(userFixtureRelPath); + const patchedFixturePath = path.resolve(os.tmpdir(), patchedFixtureFileName); + fs.copyFileSync(userFixturePath, patchedFixturePath); + + return patchedFixturePath; +}; + +const _getRelativeFixturePath = (rnVersion, fixtureFileName) => { + const path = require('node:path'); + return path.join('autolink', 'fixtures', `rn${rnVersion}`, fixtureFileName); +}; + +module.exports = { + prepareFixtureDuplicate, + prepareFixtureDuplicate77: ({ userFixtureFileName, patchedFixtureFileName }) => + prepareFixtureDuplicate({ rnVersion: '77', userFixtureFileName, patchedFixtureFileName }), +}; diff --git a/autolink/postlink/__mocks__/log.js b/autolink/postlink/__mocks__/log.js new file mode 100644 index 00000000000..1a4e22482ed --- /dev/null +++ b/autolink/postlink/__mocks__/log.js @@ -0,0 +1,11 @@ +module.exports = { + log: console.log, + logn: console.log, + warn: console.log, + warnn: console.log, + info: console.log, + infon: console.log, + debug: console.log, + debugn: console.log, + errorn: console.log, +}; diff --git a/autolink/postlink/__snapshots__/activityLinker.test.js.snap b/autolink/postlink/__snapshots__/activityLinker.test.js.snap index c08caa6e5b6..b6d8fedef60 100644 --- a/autolink/postlink/__snapshots__/activityLinker.test.js.snap +++ b/autolink/postlink/__snapshots__/activityLinker.test.js.snap @@ -1,82 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`activityLinker should work for RN 0.68 1`] = ` -"package com.app; +exports[`activityLinker should work for RN 0.77 1`] = ` +"package com.app -import com.reactnativenavigation.NavigationActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; +import com.reactnativenavigation.NavigationActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate -public class MainActivity extends NavigationActivity { - - - - - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(NavigationActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - } -} -" -`; - -exports[`activityLinker should work for RN 0.69 1`] = ` -"package com.app; - -import com.reactnativenavigation.NavigationActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; - -public class MainActivity extends NavigationActivity { - - - - - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(NavigationActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - - @Override - protected boolean isConcurrentRootEnabled() { - // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). - // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - } -} -" -`; - - -exports[`activityLinker should work for RN 0.71 1`] = ` -"package com.app; - -import com.reactnativenavigation.NavigationActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; -import com.facebook.react.defaults.DefaultReactActivityDelegate; - -public class MainActivity extends NavigationActivity { +class MainActivity : NavigationActivity() { diff --git a/autolink/postlink/__snapshots__/applicationLinker.test.js.snap b/autolink/postlink/__snapshots__/applicationLinker.test.js.snap new file mode 100644 index 00000000000..340498d4567 --- /dev/null +++ b/autolink/postlink/__snapshots__/applicationLinker.test.js.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`applicationLinker should work for RN 0.77 1`] = ` +"package com.app + +import android.app.Application +import com.facebook.react.PackageList +import com.reactnativenavigation.NavigationApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.reactnativenavigation.react.NavigationReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader + +class MainApplication : NavigationApplication() { + + override val reactNativeHost: ReactNativeHost = + object : DefaultNavigationReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + + + } +} +" +`; diff --git a/autolink/postlink/__snapshots__/gradleLinker.test.js.snap b/autolink/postlink/__snapshots__/gradleLinker.test.js.snap new file mode 100644 index 00000000000..5e035fbe6b3 --- /dev/null +++ b/autolink/postlink/__snapshots__/gradleLinker.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`gradleLinker should work for RN 0.77 1`] = ` +"buildscript { + ext { + RNNKotlinVersion = "2.0.21" + buildToolsVersion = "35.0.0" + minSdkVersion = 24 + compileSdkVersion = 35 + targetSdkVersion = 35 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.0.21" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +apply plugin: "com.facebook.react.rootproject" +" +`; diff --git a/autolink/postlink/activityLinker.js b/autolink/postlink/activityLinker.js index 47215492be1..d1c1cd1b5d6 100644 --- a/autolink/postlink/activityLinker.js +++ b/autolink/postlink/activityLinker.js @@ -5,7 +5,7 @@ var { errorn, warnn, logn, infon, debugn } = require('./log'); class ActivityLinker { constructor() { - this.activityPath = path.mainActivityJava; + this.activityPath = path.mainActivityKotlin this.extendNavigationActivitySuccess = false; this.removeGetMainComponentNameSuccess = false; this.removeCreateReactActivityDelegate = false; @@ -14,7 +14,7 @@ class ActivityLinker { link() { if (!this.activityPath) { errorn( - ' MainActivity not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' + ' MainActivity.kt not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' ); return; } @@ -41,26 +41,26 @@ class ActivityLinker { fs.writeFileSync(this.activityPath, activityContent); if (this.extendNavigationActivitySuccess && this.removeGetMainComponentNameSuccess) { - infon('MainActivity linked successfully!\n'); + infon('MainActivity.kt linked successfully!\n'); } else if (!this.extendNavigationActivitySuccess && !this.removeGetMainComponentNameSuccess) { errorn( - 'MainActivity was not linked. Please check the logs above for more information and proceed with manual linking of the MainActivity file in Android:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' + 'MainActivity.kt was not linked. Please check the logs above for more information and proceed with manual linking of the MainActivity file in Android:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' ); } else { warnn( - 'MainActivity was only partially linked. Please check the logs above for more information and proceed with manual linking for the failed steps:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' + 'MainActivity.kt was only partially linked. Please check the logs above for more information and proceed with manual linking for the failed steps:\nhttps://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava' ); } } _removeGetMainComponentName(contents) { - var match = /\/\*\*\s*\n([^\*]|(\*(?!\/)))*\*\/\s*@Override\s*protected\s*String\s*getMainComponentName\s*\(\)\s*{\s*return.+\s*\}/.exec( + var match = /(\/\*\*[\s\S]*?\*\/\s*)?override\s+fun\s+getMainComponentName\s*\(\s*\)\s*:\s*String\s*(\{\s*return[\s\S]*?\}|=[\s\S]*?)/.exec( contents ); if (match) { debugn(' Removing getMainComponentName function'); return contents.replace( - /\/\*\*\s*\n([^\*]|(\*(?!\/)))*\*\/\s*@Override\s*protected\s*String\s*getMainComponentName\s*\(\)\s*{\s*return.+\s*\}/, + /(\/\*\*[\s\S]*?\*\/\s*)?override\s+fun\s+getMainComponentName\s*\(\s*\)\s*:\s*String\s*(\{\s*return[\s\S]*?\}|=[\s\S]*?(?=\n\s*(?:\/\*\*|override|fun|\}|$)))/, '' ); } @@ -77,14 +77,10 @@ class ActivityLinker { if (this._doesActivityExtendReactActivity(activityContent)) { debugn(' Extending NavigationActivity'); return activityContent - .replace(/extends\s+ReactActivity\s*/, 'extends NavigationActivity ') + .replace(/:\s*ReactActivity\(\)\s*/, ': NavigationActivity() ') .replace( - /public\s+MainActivityDelegate\s*\(\s*ReactActivity\s+activity,\s*String\s+mainComponentName\s*\)/, - 'public MainActivityDelegate(NavigationActivity activity, String mainComponentName)' - ) - .replace( - 'import com.facebook.react.ReactActivity;', - 'import com.reactnativenavigation.NavigationActivity;' + 'import com.facebook.react.ReactActivity', + 'import com.reactnativenavigation.NavigationActivity' ); } @@ -94,18 +90,18 @@ class ActivityLinker { } _doesActivityExtendReactActivity(activityContent) { - return /public\s+class\s+MainActivity\s+extends\s+ReactActivity\s*/.test(activityContent); + return /class\s+MainActivity\s*:\s*ReactActivity\(\)\s*/.test(activityContent); } _hasAlreadyExtendNavigationActivity(activityContent) { - return /public\s+class\s+MainActivity\s+extends\s+NavigationActivity\s*/.test(activityContent); + return /class\s+MainActivity\s*:\s*NavigationActivity\(\)\s*/.test(activityContent); } _removeCreateReactActivityDelegate(activityContent) { if (this._hasCreateReactActivityDelegate(activityContent)) { debugn(' Removing createReactActivityDelegate function'); return activityContent.replace( - /\/\*\*\s*\n([^\*]|(\*(?!\/)))*\*\/\s*@Override\s*protected\s*ReactActivityDelegate\s*createReactActivityDelegate\s*\(\)\s*{\s*return((.|\r|\s)*?)}/, + /(\/\*\*[\s\S]*?\*\/\s*)?override\s+fun\s+createReactActivityDelegate\s*\(\s*\)\s*:\s*ReactActivityDelegate\s*(\{\s*return[\s\S]*?\}|=[\s\S]*?(?=\n\s*(?:\/\*\*|override|fun|\}|$)))/, '' ); } else { @@ -115,7 +111,7 @@ class ActivityLinker { } _hasCreateReactActivityDelegate(activityContent) { - return /\/\*\*\s*\n([^\*]|(\*(?!\/)))*\*\/\s*@Override\s*protected\s*ReactActivityDelegate\s*createReactActivityDelegate\s*\(\)\s*{\s*return((.|\r|\s)*?)}/.test( + return /(\/\*\*[\s\S]*?\*\/\s*)?override\s+fun\s+createReactActivityDelegate\s*\(\s*\)\s*:\s*ReactActivityDelegate\s*(\{\s*return[\s\S]*?\}|=[\s\S]*?)/.test( activityContent ); } diff --git a/autolink/postlink/activityLinker.test.js b/autolink/postlink/activityLinker.test.js index 4e771139b46..d601aca09b2 100644 --- a/autolink/postlink/activityLinker.test.js +++ b/autolink/postlink/activityLinker.test.js @@ -1,102 +1,26 @@ -import fs from 'fs'; +import fs from 'node:fs'; +import * as mockHelpers from './__helpers__/fixtures'; -/** - * Mocks - */ - -jest.mock('../postlink/log', () => ({ - log: console.log, - logn: console.log, - warn: console.log, - warnn: console.log, - info: console.log, - infon: console.log, - debug: console.log, - debugn: console.log, - errorn: console.log, -})); - -/** - * Tests - */ +jest.mock('./log'); describe('activityLinker', () => { - beforeEach(() => {}); - - it('should work for RN 0.68', () => { - jest.mock('../postlink/path', () => { - const { copyFileSync } = require('fs'); - const { tmpdir } = require('os'); - const path = require('path'); - - const tmpMainActivityPath = path.resolve(tmpdir(), 'rnn-tests_MainActivity.java'); - - copyFileSync( - path.resolve('autolink/fixtures/rn68/MainActivity.java.template'), - tmpMainActivityPath - ); + it('should work for RN 0.77', () => { + jest.mock('./path', () => { + const mainActivityPath = mockHelpers.prepareFixtureDuplicate77({ + rnVersion: '77', + userFixtureFileName: 'MainActivity.kt.template', + patchedFixtureFileName: 'rnn-tests_MainActivity.kt', + }); return { - mainActivityJava: tmpMainActivityPath, + mainActivityKotlin: mainActivityPath, }; }); const ActivityLinker = require('./activityLinker'); const linker = new ActivityLinker(); - linker.link(); - const mainActivityContent = fs.readFileSync(linker.activityPath, 'utf8'); - expect(mainActivityContent).toMatchSnapshot(); - }); - it('should work for RN 0.69', () => { - jest.mock('../postlink/path', () => { - const { copyFileSync } = require('fs'); - const { tmpdir } = require('os'); - const path = require('path'); - - const tmpMainActivityPath = path.resolve(tmpdir(), 'rnn-tests_MainActivity.java'); - - copyFileSync( - path.resolve('autolink/fixtures/rn69/MainActivity.java.template'), - tmpMainActivityPath - ); - - return { - mainActivityJava: tmpMainActivityPath, - }; - }); - - const ActivityLinker = require('./activityLinker'); - const linker = new ActivityLinker(); - - linker.link(); - const mainActivityContent = fs.readFileSync(linker.activityPath, 'utf8'); - expect(mainActivityContent).toMatchSnapshot(); - }); - - it('should work for RN 0.71', () => { - jest.mock('../postlink/path', () => { - const { copyFileSync } = require('fs'); - const { tmpdir } = require('os'); - const path = require('path'); - - const tmpMainActivityPath = path.resolve(tmpdir(), 'rnn-tests_MainActivity.java'); - - copyFileSync( - path.resolve('autolink/fixtures/rn71/MainActivity.java.template'), - tmpMainActivityPath - ); - - return { - mainActivityJava: tmpMainActivityPath, - }; - }); - - const ActivityLinker = require('./activityLinker'); - const linker = new ActivityLinker(); - - linker.link(); const mainActivityContent = fs.readFileSync(linker.activityPath, 'utf8'); expect(mainActivityContent).toMatchSnapshot(); }); diff --git a/autolink/postlink/appDelegateLinker.test.js b/autolink/postlink/appDelegateLinker.test.js index 40f1eed26c8..c3e2a55bc22 100644 --- a/autolink/postlink/appDelegateLinker.test.js +++ b/autolink/postlink/appDelegateLinker.test.js @@ -1,43 +1,17 @@ -import fs from 'fs'; +import fs from 'node:fs'; +import * as mockHelpers from './__helpers__/fixtures'; -/** - * Mocks - */ - -jest.mock('../postlink/log', () => ({ - log: console.log, - logn: console.log, - warn: console.log, - warnn: console.log, - info: console.log, - infon: console.log, - debug: console.log, - debugn: console.log, - errorn: console.log, -})); - -/** - * Tests - */ +jest.mock('./log'); describe('appDelegateLinker', () => { - beforeEach(() => { }); - it('should work for RN 0.77 with Objective-C', () => { - jest.mock('../postlink/path', () => { - const { copyFileSync } = require('fs'); - const { tmpdir } = require('os'); - const path = require('path'); - - const tmpAppDelegatePath = path.resolve(tmpdir(), 'rnn-tests_AppDelegate.mm'); - - copyFileSync( - path.resolve('autolink/fixtures/rn77/AppDelegate.mm.template'), - tmpAppDelegatePath - ); - + jest.mock('./path', () => { + const appDelegatePath = mockHelpers.prepareFixtureDuplicate77({ + userFixtureFileName: 'AppDelegate.mm.template', + patchedFixtureFileName: 'rnn-tests_AppDelegate.mm', + }); return { - appDelegate: tmpAppDelegatePath, + appDelegate: appDelegatePath, }; }); @@ -50,17 +24,11 @@ describe('appDelegateLinker', () => { }); it('should work for RN 0.77 with Swift', () => { - jest.mock('../postlink/path', () => { - const { copyFileSync } = require('fs'); - const { tmpdir } = require('os'); - const path = require('path'); - - const tmpAppDelegatePath = path.resolve(tmpdir(), 'rnn-tests_AppDelegate.swift'); - - copyFileSync( - path.resolve('autolink/fixtures/rn77/AppDelegate.swift.template'), - tmpAppDelegatePath - ); + jest.mock('./path', () => { + const tmpAppDelegatePath = mockHelpers.prepareFixtureDuplicate77({ + userFixtureFileName: 'AppDelegate.swift.template', + patchedFixtureFileName: 'rnn-tests_AppDelegate.swift', + }); return { appDelegate: tmpAppDelegatePath, @@ -69,8 +37,8 @@ describe('appDelegateLinker', () => { const AppDelegateLinker = require('./appDelegateLinker'); const linker = new AppDelegateLinker(); - linker.link(); + const appDelegateContent = fs.readFileSync(linker.appDelegatePath, 'utf8'); expect(appDelegateContent).toMatchSnapshot(); }); diff --git a/autolink/postlink/applicationLinker.js b/autolink/postlink/applicationLinker.js index 5d65b320e06..3452735de9b 100644 --- a/autolink/postlink/applicationLinker.js +++ b/autolink/postlink/applicationLinker.js @@ -5,16 +5,19 @@ var { warnn, logn, infon, debugn, errorn } = require('./log'); class ApplicationLinker { constructor() { - this.applicationPath = path.mainApplicationJava; + this.applicationPath = path.mainApplicationKotlin; this.navigationApplicationSuccess = false; this.navigationHostSuccess = false; this.soLoaderInitSuccess = false; + this.newArchEntryPointLoadSuccess = false; } link() { if (!this.applicationPath) { errorn( - 'MainApplication.java not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' + 'MainApplication.kt not found! Does the file exist in the correct folder?\n' + + ' Please check the manual installation docs:\n' + + ' https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' ); } @@ -39,26 +42,34 @@ class ApplicationLinker { } catch (e) { errorn(' ' + e); } + try { + applicationContents = this._removeNewArchEntryPointLoad(applicationContents); + this.newArchEntryPointLoadSuccess = true; + } catch (e) { + errorn(' ' + e); + } fs.writeFileSync(this.applicationPath, applicationContents); if ( this.navigationApplicationSuccess && this.navigationHostSuccess && - this.soLoaderInitSuccess + this.soLoaderInitSuccess && + this.newArchEntryPointLoadSuccess ) { - infon('MainApplication.java linked successfully!\n'); + infon('MainApplication.kt linked successfully!\n'); } else if ( !this.navigationApplicationSuccess && !this.navigationHostSuccess && - !this.soLoaderInitSuccess + !this.soLoaderInitSuccess && + !this.newArchEntryPointLoadSuccess ) { errorn( - 'MainApplication.java was not successfully linked! Please check the information above:\n https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' + 'MainApplication.kt was not successfully linked! Please check the information above:\n https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' ); } else { warnn( - 'MainApplication.java was partially linked. Please check the information above and complete the missing steps manually:\n https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' + 'MainApplication.kt was partially linked. Please check the information above and complete the missing steps manually:\n https://wix.github.io/react-native-navigation/docs/installing#3-update-mainapplicationjava' ); } } @@ -68,12 +79,12 @@ class ApplicationLinker { debugn(' Extending NavigationApplication'); return applicationContent .replace( - /extends\s+Application\s+implements\s+ReactApplication/gi, - 'extends NavigationApplication' + /:\s*Application\(\)\s*,\s*ReactApplication/gi, + ': NavigationApplication()' ) .replace( - 'import com.facebook.react.ReactApplication;', - 'import com.reactnativenavigation.NavigationApplication;' + 'import com.facebook.react.ReactApplication', + 'import com.reactnativenavigation.NavigationApplication' ); } @@ -88,13 +99,13 @@ class ApplicationLinker { } _doesExtendApplication(applicationContent) { - return /\s+MainApplication\s+extends\s+Application\s+implements\s+ReactApplication\s+/.test( + return /\s+MainApplication\s*:\s*Application\(\)\s*,\s*ReactApplication\s+/.test( applicationContent ); } _hasAlreadyLinkedApplication(applicationContent) { - return /\s+extends\s+NavigationApplication\s+/.test(applicationContent); + return /\s*:\s*NavigationApplication\(\)\s*/.test(applicationContent); } _extendNavigationHost(applicationContent) { @@ -106,18 +117,18 @@ class ApplicationLinker { if (this._doesExtendReactNativeHost(applicationContent)) { debugn(' Changing host implementation to NavigationReactNativeHost'); return applicationContent - .replace('new ReactNativeHost(this)', 'new NavigationReactNativeHost(this)') + .replace('ReactNativeHost(this)', 'NavigationReactNativeHost(this)') .replace( - 'import com.facebook.react.ReactNativeHost;', - 'import com.facebook.react.ReactNativeHost;\nimport com.reactnativenavigation.react.NavigationReactNativeHost;' + 'import com.facebook.react.ReactNativeHost', + 'import com.facebook.react.ReactNativeHost\nimport com.reactnativenavigation.react.NavigationReactNativeHost' ); } else if (this._doesExtendDefaultReactNativeHost(applicationContent)) { debugn(' Changing host implementation to NavigationReactNativeHost'); return applicationContent - .replace('new DefaultReactNativeHost(this)', 'new NavigationReactNativeHost(this)') + .replace('DefaultReactNativeHost(this)', 'NavigationReactNativeHost(this)') .replace( - 'import com.facebook.react.defaults.DefaultReactNativeHost;', - 'import com.facebook.react.defaults.DefaultReactNativeHost;\nimport com.reactnativenavigation.react.NavigationReactNativeHost;' + 'import com.facebook.react.defaults.DefaultReactNativeHost', + 'import com.facebook.react.defaults.DefaultReactNativeHost\nimport com.reactnativenavigation.react.NavigationReactNativeHost' ); } @@ -125,22 +136,22 @@ class ApplicationLinker { } _doesExtendReactNativeHost(applicationContent) { - return /\s*new ReactNativeHost\(this\)\s*/.test(applicationContent); + return /\s*ReactNativeHost\(this\)\s*/.test(applicationContent); } _doesExtendDefaultReactNativeHost(applicationContent) { - return /\s*new DefaultReactNativeHost\(this\)\s*/.test(applicationContent); + return /\s*DefaultReactNativeHost\(this\)\s*/.test(applicationContent); } _hasAlreadyLinkedNavigationHost(applicationContent) { - return /\s*new NavigationReactNativeHost\(this\)\s*/.test(applicationContent); + return /\s*NavigationReactNativeHost\(this\)\s*/.test(applicationContent); } _removeSOLoaderInit(applicationContent) { if (this._isSOLoaderInitCalled(applicationContent)) { debugn(' Removing call to SOLoader.init()'); return applicationContent.replace( - /SoLoader.init\(\s*this\s*,\s*[/* native exopackage */]*\s*false\s*\);/, + /SoLoader\.init\(\s*this\s*,\s*OpenSourceMergedSoMapping\s*\);?/, '' ); } @@ -149,7 +160,23 @@ class ApplicationLinker { } _isSOLoaderInitCalled(applicationContent) { - return /SoLoader.init\(this,\s*[/* native exopackage */]*\s*false\);/.test(applicationContent); + return /SoLoader\.init\(\s*this\s*,\s*OpenSourceMergedSoMapping\s*\);?/.test(applicationContent); + } + + _removeNewArchEntryPointLoad(applicationContent) { + if (this._isNewArchEntryPointLoadCalled(applicationContent)) { + debugn(' Removing New Architecture entry point load block'); + return applicationContent.replace( + /if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{[\s\S]*?load\(\)\s*[\s}]*?\}/, + '' + ); + } + warnn(' New Architecture entry point load block is not called, skipping.'); + return applicationContent; + } + + _isNewArchEntryPointLoadCalled(applicationContent) { + return /if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{[\s\S]*?load\(\)\s*[\s}]*?\}/.test(applicationContent); } } diff --git a/autolink/postlink/applicationLinker.test.js b/autolink/postlink/applicationLinker.test.js new file mode 100644 index 00000000000..bf219d25dc4 --- /dev/null +++ b/autolink/postlink/applicationLinker.test.js @@ -0,0 +1,25 @@ +import fs from 'node:fs'; +import * as mockHelpers from './__helpers__/fixtures'; + +jest.mock('./log'); + +describe('applicationLinker', () => { + it('should work for RN 0.77', () => { + jest.mock('./path', () => { + const mainApplicationPath = mockHelpers.prepareFixtureDuplicate77({ + userFixtureFileName: 'MainApplication.kt.template', + patchedFixtureFileName: 'rnn-tests_MainApplication.kt', + }); + return { + mainApplicationKotlin: mainApplicationPath, + }; + }); + + const ApplicationLinker = require('./applicationLinker'); + const linker = new ApplicationLinker(); + linker.link(); + + const mainApplicationContent = fs.readFileSync(linker.applicationPath, 'utf8'); + expect(mainApplicationContent).toMatchSnapshot(); + }); +}); diff --git a/autolink/postlink/gradleLinker.js b/autolink/postlink/gradleLinker.js index 520539591bb..591ecc31d6b 100644 --- a/autolink/postlink/gradleLinker.js +++ b/autolink/postlink/gradleLinker.js @@ -2,16 +2,14 @@ var path = require('./path'); var fs = require('fs'); var { warnn, errorn, logn, infon, debugn } = require('./log'); -var { insertString } = require('./stringUtils'); -var DEFAULT_KOTLIN_VERSION = '1.7.10'; +var DEFAULT_KOTLIN_VERSION = '2.0.21'; // This should be the minSdkVersion required for RNN. -var DEFAULT_MIN_SDK_VERSION = 21; +var DEFAULT_MIN_SDK_VERSION = 24; class GradleLinker { constructor() { this.gradlePath = path.rootGradle; this.setKlotinVersionSuccess = false; - this.setKotlinPluginDependencySuccess = false; this.setMinSdkVersionSuccess = false; } @@ -32,12 +30,6 @@ class GradleLinker { } catch (e) { errorn(' ' + e); } - try { - contents = this._setKotlinPluginDependency(contents); - this.setKotlinPluginDependencySuccess = true; - } catch (e) { - errorn(' ' + e); - } try { contents = this._setMinSdkVersion(contents); this.setMinSdkVersionSuccess = true; @@ -49,13 +41,11 @@ class GradleLinker { if ( this.setKlotinVersionSuccess && - this.setKotlinPluginDependencySuccess && this.setMinSdkVersionSuccess ) { infon('Root build.gradle linked successfully!\n'); } else if ( !this.setKlotinVersionSuccess && - !this.setKotlinPluginDependencySuccess && !this.setMinSdkVersionSuccess ) { errorn( @@ -68,24 +58,6 @@ class GradleLinker { } } - _setKotlinPluginDependency(contents) { - if (this._isKotlinPluginDeclared(contents)) { - warnn(' Kotlin plugin already declared'); - return contents; - } - - var match = /classpath\s*\(*["']com\.android\.tools\.build:gradle.*/.exec(contents); - if (match) { - debugn(' Adding Kotlin plugin'); - return insertString( - contents, - match.index, - `classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$RNNKotlinVersion"\n ` - ); - } else { - throw new Error(' Could not add kotlin plugin dependency'); - } - } _setKotlinVersion(contents) { if (this._isKotlinVersionSpecified(contents)) { @@ -168,13 +140,6 @@ class GradleLinker { _isKotlinVersionSpecified(contents) { return /RNNKotlinVersion/.test(contents); } - - /** - * @param {string} contents - */ - _isKotlinPluginDeclared(contents) { - return /org.jetbrains.kotlin:kotlin-gradle-plugin:/.test(contents); - } } module.exports = GradleLinker; diff --git a/autolink/postlink/gradleLinker.test.js b/autolink/postlink/gradleLinker.test.js new file mode 100644 index 00000000000..daa1b3e0124 --- /dev/null +++ b/autolink/postlink/gradleLinker.test.js @@ -0,0 +1,26 @@ +import fs from 'fs'; +import * as mockHelpers from './__helpers__/fixtures'; + +jest.mock('./log'); + +describe('gradleLinker', () => { + it('should work for RN 0.77', () => { + jest.mock('./path', () => { + const tmpBuildGradlePath = mockHelpers.prepareFixtureDuplicate77({ + userFixtureFileName: 'build.gradle.template', + patchedFixtureFileName: 'rnn-tests_build.gradle', + }); + + return { + rootGradle: tmpBuildGradlePath, + }; + }); + + const GradleLinker = require('./gradleLinker'); + const linker = new GradleLinker(); + linker.link(); + + const buildGradleContent = fs.readFileSync(linker.gradlePath, 'utf8'); + expect(buildGradleContent).toMatchSnapshot(); + }); +}); diff --git a/autolink/postlink/path.js b/autolink/postlink/path.js index 62c6858d76b..c3fb62d4eba 100644 --- a/autolink/postlink/path.js +++ b/autolink/postlink/path.js @@ -4,14 +4,13 @@ var ignoreFolders = { }; var { warnn, infon, debugn } = require('./log'); -exports.mainActivityJava = glob.sync('**/MainActivity.java', ignoreFolders)[0]; exports.mainActivityKotlin = glob.sync('**/MainActivity.kt', ignoreFolders)[0]; -var mainApplicationJava = glob.sync('**/MainApplication.java', ignoreFolders)[0]; -exports.mainApplicationJava = mainApplicationJava; -exports.rootGradle = mainApplicationJava.replace(/android\/app\/.*\.java/, 'android/build.gradle'); +var mainApplicationKotlin = glob.sync('**/MainApplication.kt', ignoreFolders)[0]; +exports.mainApplicationKotlin = mainApplicationKotlin; +exports.rootGradle = mainApplicationKotlin.replace(/android\/app\/.*\.kt/, 'android/build.gradle'); var reactNativeVersion = require('../../../react-native/package.json').version; - +infon('Found React Native version: ' + reactNativeVersion); infon('Locating the AppDelegate.mm file ...'); exports.appDelegate = glob.sync( '**/AppDelegate.mm',