From 8d2c83021754bf12a5cf731925c6a7179c4dd0b8 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 4 Mar 2024 18:09:13 +0000 Subject: [PATCH] fix(react-native-cli): ensure kotlin files are updated --- packages/react-native-cli/src/lib/Insert.ts | 32 ++++---- .../src/lib/__test__/Insert.test.ts | 78 +++++++++++++++++-- .../fixtures/MainApplication-after-2.kt | 48 ++++++++++++ .../fixtures/MainApplication-after.kt | 47 +++++++++++ .../fixtures/MainApplication-before-2.kt | 46 +++++++++++ .../fixtures/MainApplication-before.kt | 45 +++++++++++ 6 files changed, 275 insertions(+), 21 deletions(-) create mode 100644 packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after-2.kt create mode 100644 packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after.kt create mode 100644 packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before-2.kt create mode 100644 packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before.kt diff --git a/packages/react-native-cli/src/lib/Insert.ts b/packages/react-native-cli/src/lib/Insert.ts index 7e95c392ce..b3c1cf8d4f 100644 --- a/packages/react-native-cli/src/lib/Insert.ts +++ b/packages/react-native-cli/src/lib/Insert.ts @@ -18,6 +18,10 @@ const BUGSNAG_JAVA_IMPORT = 'import com.bugsnag.android.Bugsnag;' const BUGSNAG_JAVA_INIT = 'Bugsnag.start(this);' const JAVA_APP_ON_CREATE_REGEX = /(public void onCreate\s*\(\)\s*\{[^]*super\.onCreate\(\);(\s*))\S/ +const BUGSNAG_KOTLIN_IMPORT = 'import com.bugsnag.android.Bugsnag' +const BUGSNAG_KOTLIN_INIT = 'Bugsnag.start(this)' +const KOTLIN_APP_ON_CREATE_REGEX = /(override fun onCreate\s*\(\)\s*\{[^]*super\.onCreate\(\)(\s*))\S/ // /(override fun onCreate\(savedInstanceState: Bundle\?\) \{\s*super\.onCreate\(savedInstanceState\)\s*\n)/ + const DOCS_LINK = 'https://docs.bugsnag.com/platforms/react-native/react-native/#basic-configuration' const FAIL_MSG = (filename: string) => `Failed to update "${filename}" automatically. The file may not exist or it may be in an unexpected format or location. @@ -114,39 +118,39 @@ export async function insertAndroid (projectRoot: string, logger: Logger): Promi cwd: javaDir }))[0] - const relativeMainApplicationPathOther = (await asyncGlob('**/*/MainApplication', { - cwd: javaDir - }))[0] - - const relativeMainApplicationPath = relativeMainApplicationPathJava || relativeMainApplicationPathKotlin || relativeMainApplicationPathOther + const relativeMainApplicationPath = relativeMainApplicationPathJava || relativeMainApplicationPathKotlin if (!relativeMainApplicationPath) { - return logger.warn(FAIL_MSG('MainApplication.java, MainApplication.kt or MainApplication')) + return logger.warn(FAIL_MSG('MainApplication')) } mainApplicationPath = path.join(javaDir, relativeMainApplicationPath) } catch (e) { - logger.warn(FAIL_MSG('MainApplication.java, MainApplication.kt or MainApplication')) + logger.warn(FAIL_MSG('MainApplication')) return } try { - const mainApplication = await fs.readFile(mainApplicationPath, 'utf8') + const isKotlin = path.extname(mainApplicationPath) === '.kt' + const bugsnagImport = isKotlin ? BUGSNAG_KOTLIN_IMPORT : BUGSNAG_JAVA_IMPORT + const bugsnagInit = isKotlin ? BUGSNAG_KOTLIN_INIT : BUGSNAG_JAVA_INIT + const onCreateRegex = isKotlin ? KOTLIN_APP_ON_CREATE_REGEX : JAVA_APP_ON_CREATE_REGEX - if (mainApplication.includes(BUGSNAG_JAVA_IMPORT) || mainApplication.includes(BUGSNAG_JAVA_INIT)) { + const mainApplication = await fs.readFile(mainApplicationPath, 'utf8') + if (mainApplication.includes(bugsnagImport) || mainApplication.includes(bugsnagInit)) { logger.warn('Bugsnag is already included, skipping') return } - const mainApplicationWithImport = mainApplication.replace('import', `${BUGSNAG_JAVA_IMPORT}\nimport`) - const onCreateRes = JAVA_APP_ON_CREATE_REGEX.exec(mainApplicationWithImport) + const mainApplicationWithImport = mainApplication.replace('import', `${bugsnagImport}\nimport`) + const onCreateRes = onCreateRegex.exec(mainApplicationWithImport) if (!onCreateRes) { - logger.warn(FAIL_MSG('MainApplication.java, MainApplication.kt or MainApplication')) + logger.warn(FAIL_MSG('MainApplication')) return } - await fs.writeFile(mainApplicationPath, mainApplicationWithImport.replace(onCreateRes[1], `${onCreateRes[1]}${BUGSNAG_JAVA_INIT}${onCreateRes[2]}`), 'utf8') + await fs.writeFile(mainApplicationPath, mainApplicationWithImport.replace(onCreateRes[1], `${onCreateRes[1]}${bugsnagInit}${onCreateRes[2]}`), 'utf8') logger.success('Done') } catch (e) { - logger.error(FAIL_MSG('MainApplication.java, MainApplication.kt or MainApplication')) + logger.error(FAIL_MSG('MainApplication')) } } diff --git a/packages/react-native-cli/src/lib/__test__/Insert.test.ts b/packages/react-native-cli/src/lib/__test__/Insert.test.ts index c7d69ab6aa..e128d797b0 100644 --- a/packages/react-native-cli/src/lib/__test__/Insert.test.ts +++ b/packages/react-native-cli/src/lib/__test__/Insert.test.ts @@ -185,7 +185,7 @@ test('insertIos(): no identifiable app launch method', async () => { expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.mm" automatically.')) }) -test('insertAndroid(): success', async () => { +test('insertAndroid(): success (java)', async () => { const globMock = glob as unknown as jest.MockedFunction globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.java'])) @@ -207,7 +207,29 @@ test('insertAndroid(): success', async () => { ) }) -test('insertAndroid(): success, tolerates some differences in source', async () => { +test('insertAndroid(): success (kotlin)', async () => { + const globMock = glob as unknown as jest.MockedFunction + globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.kt'])) + + const mainApplication = await loadFixture(path.join(__dirname, 'fixtures', 'MainApplication-before.kt')) + const readFileMock = fs.readFile as jest.MockedFunction + readFileMock.mockResolvedValue(mainApplication) + + const writeFileMock = fs.writeFile as jest.MockedFunction + + await insertAndroid('/random/path', logger) + expect(readFileMock).toHaveBeenCalledWith( + '/random/path/android/app/src/main/java/com/bugsnagreactnativeclitest/MainApplication.kt', + 'utf8' + ) + expect(writeFileMock).toHaveBeenCalledWith( + '/random/path/android/app/src/main/java/com/bugsnagreactnativeclitest/MainApplication.kt', + await loadFixture(path.join(__dirname, 'fixtures', 'MainApplication-after.kt')), + 'utf8' + ) +}) + +test('insertAndroid(): success, tolerates some differences in source (java)', async () => { const globMock = glob as unknown as jest.MockedFunction globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.java'])) @@ -229,7 +251,29 @@ test('insertAndroid(): success, tolerates some differences in source', async () ) }) -test('insertAndroid(): already present', async () => { +test('insertAndroid(): success, tolerates some differences in source (kotlin)', async () => { + const globMock = glob as unknown as jest.MockedFunction + globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.kt'])) + + const mainApplication = await loadFixture(path.join(__dirname, 'fixtures', 'MainApplication-before-2.kt')) + const readFileMock = fs.readFile as jest.MockedFunction + readFileMock.mockResolvedValue(mainApplication) + + const writeFileMock = fs.writeFile as jest.MockedFunction + + await insertAndroid('/random/path', logger) + expect(readFileMock).toHaveBeenCalledWith( + '/random/path/android/app/src/main/java/com/bugsnagreactnativeclitest/MainApplication.kt', + 'utf8' + ) + expect(writeFileMock).toHaveBeenCalledWith( + '/random/path/android/app/src/main/java/com/bugsnagreactnativeclitest/MainApplication.kt', + await loadFixture(path.join(__dirname, 'fixtures', 'MainApplication-after-2.kt')), + 'utf8' + ) +}) + +test('insertAndroid(): already present (java)', async () => { const globMock = glob as unknown as jest.MockedFunction globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.java'])) @@ -249,6 +293,26 @@ test('insertAndroid(): already present', async () => { expect(logger.warn).toHaveBeenCalledWith('Bugsnag is already included, skipping') }) +test('insertAndroid(): already present (kotlin)', async () => { + const globMock = glob as unknown as jest.MockedFunction + globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.kt'])) + + const mainApplication = await loadFixture(path.join(__dirname, 'fixtures', 'MainApplication-after.kt')) + const readFileMock = fs.readFile as jest.MockedFunction + readFileMock.mockResolvedValue(mainApplication) + + const writeFileMock = fs.writeFile as jest.MockedFunction + + await insertAndroid('/random/path', logger) + expect(readFileMock).toHaveBeenCalledWith( + '/random/path/android/app/src/main/java/com/bugsnagreactnativeclitest/MainApplication.kt', + 'utf8' + ) + expect(writeFileMock).not.toHaveBeenCalled() + + expect(logger.warn).toHaveBeenCalledWith('Bugsnag is already included, skipping') +}) + test('insertAndroid(): failure to locate file', async () => { const globMock = glob as unknown as jest.MockedFunction globMock.mockImplementation((glob, opts, cb) => cb(null, ['com/bugsnagreactnativeclitest/MainApplication.java'])) @@ -265,7 +329,7 @@ test('insertAndroid(): failure to locate file', async () => { ) expect(writeFileMock).not.toHaveBeenCalled() - expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication.java, MainApplication.kt or MainApplication" automatically.')) + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication" automatically.')) }) test('insertAndroid(): failure to locate package directory', async () => { @@ -279,7 +343,7 @@ test('insertAndroid(): failure to locate package directory', async () => { expect(readFileMock).not.toHaveBeenCalled() expect(writeFileMock).not.toHaveBeenCalled() - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication.java, MainApplication.kt or MainApplication" automatically.')) + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication" automatically.')) }) test('insertAndroid(): project directory error', async () => { @@ -293,7 +357,7 @@ test('insertAndroid(): project directory error', async () => { expect(readFileMock).not.toHaveBeenCalled() expect(writeFileMock).not.toHaveBeenCalled() - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication.java, MainApplication.kt or MainApplication" automatically.')) + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication" automatically.')) }) test('insertAndroid(): no identifiable onCreate method', async () => { @@ -311,5 +375,5 @@ test('insertAndroid(): no identifiable onCreate method', async () => { ) expect(writeFileMock).not.toHaveBeenCalled() - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication.java, MainApplication.kt or MainApplication" automatically.')) + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "MainApplication" automatically.')) }) diff --git a/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after-2.kt b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after-2.kt new file mode 100644 index 0000000000..873c8130e8 --- /dev/null +++ b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after-2.kt @@ -0,0 +1,48 @@ +package com.bugsnagreactnativeclitest + +import com.bugsnag.android.Bugsnag +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.flipper.ReactNativeFlipper +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(this.applicationContext, reactNativeHost) + + override fun onCreate() { + // something between curly brace and super call + super.onCreate() + Bugsnag.start(this) + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + } +} diff --git a/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after.kt b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after.kt new file mode 100644 index 0000000000..67c3edb782 --- /dev/null +++ b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-after.kt @@ -0,0 +1,47 @@ +package com.bugsnagreactnativeclitest + +import com.bugsnag.android.Bugsnag +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.flipper.ReactNativeFlipper +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(this.applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + Bugsnag.start(this) + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + } +} diff --git a/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before-2.kt b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before-2.kt new file mode 100644 index 0000000000..52b7349533 --- /dev/null +++ b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before-2.kt @@ -0,0 +1,46 @@ +package com.bugsnagreactnativeclitest + +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.flipper.ReactNativeFlipper +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(this.applicationContext, reactNativeHost) + + override fun onCreate() { + // something between curly brace and super call + super.onCreate() + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + } +} diff --git a/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before.kt b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before.kt new file mode 100644 index 0000000000..71fe2e5995 --- /dev/null +++ b/packages/react-native-cli/src/lib/__test__/fixtures/MainApplication-before.kt @@ -0,0 +1,45 @@ +package com.bugsnagreactnativeclitest + +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.flipper.ReactNativeFlipper +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(this.applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + load() + } + ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) + } +}