Skip to content

Commit

Permalink
Fix RN CLI 'insert' command on RN 0.68+
Browse files Browse the repository at this point in the history
RN 0.68 changed 'AppDelegate.m' to 'AppDelegate.mm', which broke our
previous logic for finding the file
  • Loading branch information
imjoehaines committed Apr 21, 2022
1 parent 427b5de commit 88a4dc7
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- (plugin-react) Add 'children' prop to BugsnagErrorBoundary [#1723](https://github.com/bugsnag/bugsnag-js/pull/1723)
- (react-native) Fix reporting of `RCTFatal()` crashes on iOS. [#1719](https://github.com/bugsnag/bugsnag-js/pull/1719)
- (react-native-cli) Fix 'insert' command with RN 0.68+ [#1726](https://github.com/bugsnag/bugsnag-js/pull/1726)
- (plugin-electron-app-breadcrumbs) Fix a TypeError caused by using a BrowserWindow object after it is destroyed [#1722](https://github.com/bugsnag/bugsnag-js/pull/1722)

## v7.16.3 (2022-04-05)
Expand Down
38 changes: 30 additions & 8 deletions packages/react-native-cli/src/lib/Insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,34 @@ export async function insertJs (projectRoot: string, logger: Logger): Promise<vo

export async function insertIos (projectRoot: string, logger: Logger): Promise<void> {
logger.info('Adding Bugsnag to the iOS layer')

const iosDir = path.join(projectRoot, 'ios')
let xcodeprojDir
let appDelegatePath

try {
xcodeprojDir = (await fs.readdir(iosDir)).find(p => p.endsWith('.xcodeproj'))
const xcodeprojDir = (await fs.readdir(iosDir)).find(p => p.endsWith('.xcodeproj'))

if (!xcodeprojDir) {
logger.warn(FAIL_MSG('AppDelegate.m'))
logger.warn(FAIL_MSG('AppDelegate'))
return
}

const appDelegateDirectory = path.join(iosDir, xcodeprojDir.replace(/\.xcodeproj$/, ''))

// handle both AppDelegate.m and AppDelegate.mm (RN 0.68+)
const appDelegateFile = (await fs.readdir(appDelegateDirectory)).find(p => p.startsWith('AppDelegate.m'))

if (!appDelegateFile) {
logger.warn(FAIL_MSG('AppDelegate'))
return
}

appDelegatePath = path.join(iosDir, xcodeprojDir.replace(/\.xcodeproj$/, ''), appDelegateFile)
} catch (e) {
logger.error(FAIL_MSG('AppDelegate.m'))
logger.error(FAIL_MSG('AppDelegate'))
return
}
const appDelegatePath = path.join(iosDir, xcodeprojDir.replace(/\.xcodeproj$/, ''), 'AppDelegate.m')

try {
const appDelegate = await fs.readFile(appDelegatePath, 'utf8')

Expand All @@ -67,14 +82,21 @@ export async function insertIos (projectRoot: string, logger: Logger): Promise<v

const appDelegateWithImport = `${BUGSNAG_COCOA_IMPORT}\n${appDelegate}`
const appLaunchRes = COCOA_APP_LAUNCH_REGEX.exec(appDelegateWithImport)

if (!appLaunchRes) {
logger.warn(FAIL_MSG('AppDelegate.m'))
logger.warn(FAIL_MSG(path.basename(appDelegatePath)))
return
}
await fs.writeFile(appDelegatePath, appDelegateWithImport.replace(appLaunchRes[1], `${appLaunchRes[1]} ${BUGSNAG_COCOA_INIT}\n\n`), 'utf8')

await fs.writeFile(
appDelegatePath,
appDelegateWithImport.replace(appLaunchRes[1], `${appLaunchRes[1]} ${BUGSNAG_COCOA_INIT}\n\n`),
'utf8'
)

logger.success('Done')
} catch (e) {
logger.error(FAIL_MSG('AppDelegate.m'))
logger.error(FAIL_MSG(path.basename(appDelegatePath)))
}
}

Expand Down
34 changes: 23 additions & 11 deletions packages/react-native-cli/src/lib/__test__/Insert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ test('insertJs(): already present', async () => {
test('insertIos(): success', async () => {
type readdir = (path: string) => Promise<string[]>
const readdirMock = fs.readdir as unknown as jest.MockedFunction<readdir>
readdirMock.mockResolvedValue(['BugsnagReactNativeCliTest.xcodeproj'])

readdirMock
.mockResolvedValueOnce(['BugsnagReactNativeCliTest.xcodeproj'])
.mockResolvedValueOnce(['a', 'b', 'AppDelegate.m', 'c', 'd'])

const appDelegate = await loadFixture(path.join(__dirname, 'fixtures', 'AppDelegate-before.m'))
const readFileMock = fs.readFile as jest.MockedFunction<typeof fs.readFile>
Expand All @@ -94,7 +97,10 @@ test('insertIos(): success', async () => {
test('insertIos(): already present', async () => {
type readdir = (path: string) => Promise<string[]>
const readdirMock = fs.readdir as unknown as jest.MockedFunction<readdir>
readdirMock.mockResolvedValue(['BugsnagReactNativeCliTest.xcodeproj'])

readdirMock
.mockResolvedValueOnce(['BugsnagReactNativeCliTest.xcodeproj'])
.mockResolvedValueOnce(['a', 'b', 'AppDelegate.mm', 'c', 'd'])

const appDelegate = await loadFixture(path.join(__dirname, 'fixtures', 'AppDelegate-after.m'))
const readFileMock = fs.readFile as jest.MockedFunction<typeof fs.readFile>
Expand All @@ -104,7 +110,7 @@ test('insertIos(): already present', async () => {
writeFileMock.mockResolvedValue()

await insertIos('/random/path', logger)
expect(readFileMock).toHaveBeenCalledWith('/random/path/ios/BugsnagReactNativeCliTest/AppDelegate.m', 'utf8')
expect(readFileMock).toHaveBeenCalledWith('/random/path/ios/BugsnagReactNativeCliTest/AppDelegate.mm', 'utf8')
expect(writeFileMock).not.toHaveBeenCalled()

expect(logger.warn).toHaveBeenCalledWith('Bugsnag is already included, skipping')
Expand All @@ -113,18 +119,21 @@ test('insertIos(): already present', async () => {
test('insertIos(): failure to locate file', async () => {
type readdir = (path: string) => Promise<string[]>
const readdirMock = fs.readdir as unknown as jest.MockedFunction<readdir>
readdirMock.mockResolvedValue(['BugsnagReactNativeCliTest.xcodeproj'])

readdirMock
.mockResolvedValueOnce(['BugsnagReactNativeCliTest.xcodeproj'])
.mockResolvedValueOnce(['a', 'b', 'not-an-AppDelegate.x', 'c', 'd'])

const readFileMock = fs.readFile as jest.MockedFunction<typeof fs.readFile>
readFileMock.mockRejectedValue(await generateNotFoundError())

const writeFileMock = fs.writeFile as jest.MockedFunction<typeof fs.writeFile>

await insertIos('/random/path', logger)
expect(readFileMock).toHaveBeenCalledWith('/random/path/ios/BugsnagReactNativeCliTest/AppDelegate.m', 'utf8')
expect(readFileMock).not.toHaveBeenCalled()
expect(writeFileMock).not.toHaveBeenCalled()

expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.m" automatically.'))
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate" automatically.'))
})

test('insertIos(): failure to locate project directory', async () => {
Expand All @@ -139,7 +148,7 @@ test('insertIos(): failure to locate project directory', async () => {
expect(readFileMock).not.toHaveBeenCalled()
expect(writeFileMock).not.toHaveBeenCalled()

expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.m" automatically.'))
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate" automatically.'))
})

test('insertIos(): project directory error', async () => {
Expand All @@ -154,23 +163,26 @@ test('insertIos(): project directory error', async () => {
expect(readFileMock).not.toHaveBeenCalled()
expect(writeFileMock).not.toHaveBeenCalled()

expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.m" automatically.'))
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate" automatically.'))
})

test('insertIos(): no identifiable app launch method', async () => {
type readdir = (path: string) => Promise<string[]>
const readdirMock = fs.readdir as unknown as jest.MockedFunction<readdir>
readdirMock.mockResolvedValue(['BugsnagReactNativeCliTest.xcodeproj'])

readdirMock
.mockResolvedValueOnce(['BugsnagReactNativeCliTest.xcodeproj'])
.mockResolvedValueOnce(['a', 'b', 'AppDelegate.mm', 'c', 'd'])

const readFileMock = fs.readFile as jest.MockedFunction<typeof fs.readFile>
readFileMock.mockResolvedValue('not good objective c')
const writeFileMock = fs.writeFile as jest.MockedFunction<typeof fs.writeFile>

await insertIos('/random/path', logger)
expect(readFileMock).toHaveBeenCalledWith('/random/path/ios/BugsnagReactNativeCliTest/AppDelegate.m', 'utf8')
expect(readFileMock).toHaveBeenCalledWith('/random/path/ios/BugsnagReactNativeCliTest/AppDelegate.mm', 'utf8')
expect(writeFileMock).not.toHaveBeenCalled()

expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.m" automatically.'))
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to update "AppDelegate.mm" automatically.'))
})

test('insertAndroid(): success', async () => {
Expand Down

0 comments on commit 88a4dc7

Please sign in to comment.