diff --git a/packages/react-native-builder-bob/src/targets/codegen.ts b/packages/react-native-builder-bob/src/targets/codegen/index.ts similarity index 88% rename from packages/react-native-builder-bob/src/targets/codegen.ts rename to packages/react-native-builder-bob/src/targets/codegen/index.ts index e98defe8d..55ba5e67a 100644 --- a/packages/react-native-builder-bob/src/targets/codegen.ts +++ b/packages/react-native-builder-bob/src/targets/codegen/index.ts @@ -1,10 +1,11 @@ import kleur from 'kleur'; -import type { Input } from '../types'; -import { patchCodegenAndroidPackage } from '../utils/patchCodegenAndroidPackage'; +import type { Input } from '../../types'; +import { patchCodegenAndroidPackage } from './patches/patchCodegenAndroidPackage'; import fs from 'fs-extra'; import path from 'path'; import del from 'del'; -import { runRNCCli } from '../utils/runRNCCli'; +import { runRNCCli } from '../../utils/runRNCCli'; +import { removeCodegenAppLevelCode } from './patches/removeCodegenAppLevelCode'; type Options = Input; @@ -47,6 +48,7 @@ export default async function build({ root, report }: Options) { if (codegenType === 'modules' || codegenType === 'all') { await patchCodegenAndroidPackage(root, packageJson, report); } + await removeCodegenAppLevelCode(root, packageJson); report.success('Generated native code with codegen'); } catch (e: unknown) { diff --git a/packages/react-native-builder-bob/src/__tests__/patchCodegenAndroidPackage.test.ts b/packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.test.ts similarity index 94% rename from packages/react-native-builder-bob/src/__tests__/patchCodegenAndroidPackage.test.ts rename to packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.test.ts index e455db2d4..90290d563 100644 --- a/packages/react-native-builder-bob/src/__tests__/patchCodegenAndroidPackage.test.ts +++ b/packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.test.ts @@ -1,9 +1,9 @@ import { expect, it, describe, beforeEach, afterEach } from '@jest/globals'; import fs from 'fs-extra'; import path from 'node:path'; -import { patchCodegenAndroidPackage } from '../utils/patchCodegenAndroidPackage'; +import { patchCodegenAndroidPackage } from './patchCodegenAndroidPackage'; import mockfs from 'mock-fs'; -import type { Report } from '../types'; +import type { Report } from '../../../types'; const mockPackageJson = { codegenConfig: { diff --git a/packages/react-native-builder-bob/src/utils/patchCodegenAndroidPackage.ts b/packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.ts similarity index 94% rename from packages/react-native-builder-bob/src/utils/patchCodegenAndroidPackage.ts rename to packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.ts index 1868ad256..9ba2fcadf 100644 --- a/packages/react-native-builder-bob/src/utils/patchCodegenAndroidPackage.ts +++ b/packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.ts @@ -1,9 +1,9 @@ import fs from 'fs-extra'; import path from 'path'; -import type { Report } from '../types'; +import type { Report } from '../../../types'; -const CODEGEN_DOCS = - 'https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-libraries-prerequisites.md#configure-codegen'; +export const CODEGEN_DOCS = + 'https://reactnative.dev/docs/the-new-architecture/using-codegen#configuring-codegen'; /** * Currently, running react-native codegen generates java files with package name `com.facebook.fbreact.specs`. diff --git a/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.test.ts b/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.test.ts new file mode 100644 index 000000000..b8ae48063 --- /dev/null +++ b/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.test.ts @@ -0,0 +1,98 @@ +import { expect, it, describe, beforeEach, afterEach } from '@jest/globals'; +import fs from 'fs-extra'; +import path from 'node:path'; +import { removeCodegenAppLevelCode } from './removeCodegenAppLevelCode'; +import mockfs from 'mock-fs'; + +const mockPackageJson = { + codegenConfig: { + outputDir: { + android: 'android/generated', + ios: 'ios/generated', + }, + }, +}; + +const mockProjectPath = path.resolve(__dirname, 'mockProject'); + +describe('patchCodegenAndroidPackage', () => { + beforeEach(() => { + mockfs({ + [mockProjectPath]: { + 'package.json': JSON.stringify(mockPackageJson), + 'ios': { + generated: { + 'RCTAppDependencyProvider.h': '', + 'RCTAppDependencyProvider.mm': '', + 'RCTModulesConformingToProtocolsProvider.h': '', + 'RCTModulesConformingToProtocolsProvider.mm': '', + 'RCTThirdPartyComponentsProvider.h': '', + 'RCTThirdPartyComponentsProvider.mm': '', + 'ReactAppDependencyProvider.podspec': '', + }, + }, + 'android': { + generated: { + 'RCTAppDependencyProvider.h': '', + 'RCTAppDependencyProvider.mm': '', + 'RCTModulesConformingToProtocolsProvider.h': '', + 'RCTModulesConformingToProtocolsProvider.mm': '', + 'RCTThirdPartyComponentsProvider.h': '', + 'RCTThirdPartyComponentsProvider.mm': '', + 'ReactAppDependencyProvider.podspec': '', + }, + }, + }, + }); + }); + + afterEach(() => { + mockfs.restore(); + }); + + it('removes the duplicate iOS files', async () => { + await removeCodegenAppLevelCode(mockProjectPath, mockPackageJson); + + expect( + ( + await fs.promises.readdir( + path.join(mockProjectPath, 'ios', 'generated') + ) + ).length + ).toBe(0); + }); + + it('removes the unnecessary Android files', async () => { + await removeCodegenAppLevelCode(mockProjectPath, mockPackageJson); + + expect( + ( + await fs.promises.readdir( + path.join(mockProjectPath, 'android', 'generated') + ) + ).length + ).toBe(0); + }); + + it("doesn't crash the process when there are no files to remove", async () => { + mockfs({ + [mockProjectPath]: { + 'package.json': JSON.stringify(mockPackageJson), + 'ios': { + generated: { + someRandomFile: '', + }, + }, + 'android': { + generated: { + someRandomFile: '', + }, + }, + }, + }); + + await expect( + removeCodegenAppLevelCode(mockProjectPath, mockPackageJson) + ).resolves.not.toThrow(); + }); +}); diff --git a/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.ts b/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.ts new file mode 100644 index 000000000..cf2f1594d --- /dev/null +++ b/packages/react-native-builder-bob/src/targets/codegen/patches/removeCodegenAppLevelCode.ts @@ -0,0 +1,70 @@ +import fs from 'fs-extra'; +import path from 'path'; +import { CODEGEN_DOCS } from './patchCodegenAndroidPackage'; + +const FILES_TO_REMOVE = [ + 'RCTAppDependencyProvider.h', + 'RCTAppDependencyProvider.mm', + 'RCTModulesConformingToProtocolsProvider.h', + 'RCTModulesConformingToProtocolsProvider.mm', + 'RCTThirdPartyComponentsProvider.h', + 'RCTThirdPartyComponentsProvider.mm', + 'ReactAppDependencyProvider.podspec', +]; + +/** + * With React Native 0.77, calling `@react-native-community/cli codegen` generates + * some app level source files such as `RCTAppDependencyProvider.mm`. + * These files are supposed to be only generated for apps + * but the cli misbehaves and generates them for all sorts of projects. + * You can find the relevant PR here: https://github.com/facebook/react-native/pull/47650 + * This patch can be removed when this gets fixed in React Native. + */ +export async function removeCodegenAppLevelCode( + projectPath: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + packageJson: any +) { + const codegenAndroidPathSetting: string | undefined = + packageJson.codegenConfig?.outputDir?.android; + if (!codegenAndroidPathSetting) { + throw new Error( + `Your package.json doesn't contain codegenConfig.outputDir.android. Please see ${CODEGEN_DOCS}` + ); + } + const codegenAndroidPath = path.resolve( + projectPath, + codegenAndroidPathSetting + ); + + if (!(await fs.pathExists(codegenAndroidPath))) { + throw new Error( + `The codegen android path defined in your package.json: ${codegenAndroidPath} doesnt' exist.` + ); + } + + const codegenIosPathSetting: string | undefined = + packageJson.codegenConfig?.outputDir?.ios; + if (!codegenIosPathSetting) { + throw new Error( + `Your package.json doesn't contain codegenConfig.outputDir.ios. Please see ${CODEGEN_DOCS}` + ); + } + const codegenIosPath = path.resolve(projectPath, codegenIosPathSetting); + + if (!(await fs.pathExists(codegenAndroidPath))) { + throw new Error( + `The codegen iOS path defined in your package.json: ${codegenIosPathSetting} doesnt' exist.` + ); + } + + const androidPromises = FILES_TO_REMOVE.map((fileName) => + fs.rm(path.join(codegenAndroidPath, fileName)) + ); + + const iosPromises = FILES_TO_REMOVE.map((fileName) => + fs.rm(path.join(codegenIosPath, fileName)) + ); + + await Promise.allSettled([...androidPromises, ...iosPromises]); +}