From 0a677264a728f1630c67f4fd1384f38ef2f253ce Mon Sep 17 00:00:00 2001 From: Douglas Lowder Date: Sun, 3 Oct 2021 16:13:58 -0700 Subject: [PATCH] feat: Allow the upgrade command to use TV repo The upgrade command does not currently work for Apple TV and Android TV projects, because they use `react-native-tvos`, a fork of the main repo. (Issue #1476) (https://github.com/react-native-tvos/react-native-tvos/issues/224) Solution: - Provide a version of https://github.com/react-native-community/rn-diff-purge that has the diffs for the TV repo - Make `upgrade.ts` generic by detecting whether the project is using a forked repo, and having an easily extendable list of forks that are known to work. This list includes the main repo as well. ```ts type ForkNameType = 'react-native' | 'react-native-tvos'; const forks = { 'react-native': { rawDiffUrl: 'https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs', webDiffUrl: 'https://react-native-community.github.io/upgrade-helper', dependencyName: 'react-native', }, 'react-native-tvos': { rawDiffUrl: 'https://raw.githubusercontent.com/react-native-tvos/rn-diff-purge-tv/diffs/diffs', webDiffUrl: 'https://react-native-community.github.io/upgrade-helper', dependencyName: 'react-native@npm:react-native-tvos', }, }; ``` All methods in `upgrade.ts` now refer to this structure in their implementations. The upgrade unit tests have been refactored so that the test implementations and common mocks are in a common module, allowing the main repo and TV repo to be tested separately with their repo-specific mocks. --- .../__snapshots__/upgrade-tv.test.ts.snap | 167 +++++++++ .../__snapshots__/upgrade.test.ts.snap | 86 ++++- .../__tests__/upgrade-testing-methods.ts | 252 +++++++++++++ .../upgrade/__tests__/upgrade-tv.test.ts | 94 +++++ .../upgrade/__tests__/upgrade.test.ts | 336 +++--------------- packages/cli/src/commands/upgrade/upgrade.ts | 105 ++++-- 6 files changed, 719 insertions(+), 321 deletions(-) create mode 100644 packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade-tv.test.ts.snap create mode 100644 packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts create mode 100644 packages/cli/src/commands/upgrade/__tests__/upgrade-tv.test.ts diff --git a/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade-tv.test.ts.snap b/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade-tv.test.ts.snap new file mode 100644 index 000000000..b4b070d2e --- /dev/null +++ b/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade-tv.test.ts.snap @@ -0,0 +1,167 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Upgrade tests for react-native-tvos repo cleans up if patching fails, 1`] = ` +"info Fetching diff between v0.62.2-1 and v0.64.2-4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +info Applying diff... +warn Excluding files that exist in the template, but not in your project: + - .flowconfig +error Excluding files that failed to apply the diff: + - ios/MyApp.xcodeproj/project.pbxproj +Please make sure to check the actual changes after the upgrade command is finished. +You can find them in our Upgrade Helper web app: https://react-native-community.github.io/upgrade-helper/?from=0.62.2-1&to=0.64.2-4 +$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig --exclude=ios/MyApp.xcodeproj/project.pbxproj -p2 --3way --directory= +debug \\"git apply\\" failed. Error output: +error: .flowconfig: does not exist in index +error: ios/MyApp.xcodeproj/project.pbxproj: patch does not apply +error Automatically applying diff failed. We did our best to automatically upgrade as many files as possible +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +error Patch failed to apply for unknown reason. Please fall back to manual way of upgrading +warn After resolving conflicts don't forget to run \\"pod install\\" inside \\"ios\\" directory +info You may find these resources helpful: +• Release notes: https://github.com/facebook/react-native/releases/tag/v0.64.2-4 +• Manual Upgrade Helper: https://react-native-community.github.io/upgrade-helper/?from=0.62.2-1&to=0.64.2-4 +• Git diff: https://raw.githubusercontent.com/react-native-tvos/rn-diff-purge-tv/diffs/diffs/0.62.2-1..0.64.2-4.diff" +`; + +exports[`Upgrade tests for react-native-tvos repo fetches empty patch and installs deps 1`] = ` +"info Fetching diff between v0.62.2-1 and v0.64.2-4... +info Diff has no changes to apply, proceeding further +info Installing \\"react-native@0.64.2-4\\" and its peer dependencies... +$ execa npm info react-native-tvos@0.64.2-4 peerDependencies --json +$ yarn add react-native@npm:react-native-tvos@0.64.2-4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +success Upgraded React Native to v0.64.2-4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native-tvos repo fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory 1`] = ` +"info Fetching diff between v0.62.2-1 and v0.64.2-4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ +info Applying diff... +$ execa git apply tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +info Installing \\"react-native@0.64.2-4\\" and its peer dependencies... +$ execa npm info react-native-tvos@0.64.2-4 peerDependencies --json +$ yarn add react-native@npm:react-native-tvos@0.64.2-4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +info Running \\"git status\\" to check what changed... +$ execa git status +success Upgraded React Native to v0.64.2-4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native-tvos repo fetches regular patch, adds remote, applies patch, installs deps, removes remote, 1`] = ` +"info Fetching diff between v0.62.2-1 and v0.64.2-4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +info Applying diff... +$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +info Installing \\"react-native@0.64.2-4\\" and its peer dependencies... +$ execa npm info react-native-tvos@0.64.2-4 peerDependencies --json +$ yarn add react-native@npm:react-native-tvos@0.64.2-4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +info Running \\"git status\\" to check what changed... +$ execa git status +success Upgraded React Native to v0.64.2-4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native-tvos repo fetches regular patch, adds remote, applies patch, installs deps, removes remote,: RnDiffApp is replaced with app name (TestApp and com.testapp) 1`] = ` +"Snapshot Diff: +- First value ++ Second value + +@@ -1,9 +1,9 @@ +- diff --git a/RnDiffApp/android/app/src/main/AndroidManifest.xml b/RnDiffApp/android/app/src/main/AndroidManifest.xml ++ diff --git a/TestApp/android/app/src/main/AndroidManifest.xml b/TestApp/android/app/src/main/AndroidManifest.xml + index bc3a9310..f3e0d155 100644 +- --- a/RnDiffApp/android/app/src/main/AndroidManifest.xml +- +++ b/RnDiffApp/android/app/src/main/AndroidManifest.xml ++ --- a/TestApp/android/app/src/main/AndroidManifest.xml ++ +++ b/TestApp/android/app/src/main/AndroidManifest.xml + @@ -1,8 +1,7 @@ + +- + package=\\"com.rndiffapp\\"> ++ - package=\\"com.testapp\\"> ++ + package=\\"com.testapp\\"> + +@@ -14,6 +14,6 @@ + android:name=\\".MainApplication\\" +- diff --git a/RnDiffApp/ios/RnDiffApp/AppDelegate.h b/RnDiffApp/ios/RnDiffApp/AppDelegate.h ++ diff --git a/TestApp/ios/TestApp/AppDelegate.h b/TestApp/ios/TestApp/AppDelegate.h + index 4b5644f2..2726d5e1 100644 +- --- a/RnDiffApp/ios/RnDiffApp/AppDelegate.h +- +++ b/RnDiffApp/ios/RnDiffApp/AppDelegate.h ++ --- a/TestApp/ios/TestApp/AppDelegate.h ++ +++ b/TestApp/ios/TestApp/AppDelegate.h + @@ -5,9 +5,10 @@ +@@ -29,6 +29,6 @@ + @property (nonatomic, strong) UIWindow *window; +- diff --git a/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java b/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java ++ diff --git a/TestApp/android/app/src/main/java/com/testapp/MainApplication.java b/TestApp/android/app/src/main/java/com/testapp/MainApplication.java + index bc3a9310..f3e0d155 100644 +- --- a/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java +- +++ b/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java ++ --- a/TestApp/android/app/src/main/java/com/testapp/MainApplication.java ++ +++ b/TestApp/android/app/src/main/java/com/testapp/MainApplication.java +" +`; + +exports[`Upgrade tests for react-native-tvos repo works with --name-ios and --name-android: RnDiffApp is replaced with app name (CustomIos and co.uk.customandroid.app) 1`] = ` +"Snapshot Diff: +- First value ++ Second value + +@@ -1,9 +1,9 @@ +- diff --git a/RnDiffApp/android/app/src/main/AndroidManifest.xml b/RnDiffApp/android/app/src/main/AndroidManifest.xml ++ diff --git a/CustomIos/android/app/src/main/AndroidManifest.xml b/CustomIos/android/app/src/main/AndroidManifest.xml + index bc3a9310..f3e0d155 100644 +- --- a/RnDiffApp/android/app/src/main/AndroidManifest.xml +- +++ b/RnDiffApp/android/app/src/main/AndroidManifest.xml ++ --- a/CustomIos/android/app/src/main/AndroidManifest.xml ++ +++ b/CustomIos/android/app/src/main/AndroidManifest.xml + @@ -1,8 +1,7 @@ + +- + package=\\"com.rndiffapp\\"> ++ - package=\\"co.uk.customandroid.app\\"> ++ + package=\\"co.uk.customandroid.app\\"> + +@@ -14,6 +14,6 @@ + android:name=\\".MainApplication\\" +- diff --git a/RnDiffApp/ios/RnDiffApp/AppDelegate.h b/RnDiffApp/ios/RnDiffApp/AppDelegate.h ++ diff --git a/CustomIos/ios/CustomIos/AppDelegate.h b/CustomIos/ios/CustomIos/AppDelegate.h + index 4b5644f2..2726d5e1 100644 +- --- a/RnDiffApp/ios/RnDiffApp/AppDelegate.h +- +++ b/RnDiffApp/ios/RnDiffApp/AppDelegate.h ++ --- a/CustomIos/ios/CustomIos/AppDelegate.h ++ +++ b/CustomIos/ios/CustomIos/AppDelegate.h + @@ -5,9 +5,10 @@ +@@ -29,6 +29,6 @@ + @property (nonatomic, strong) UIWindow *window; +- diff --git a/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java b/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java ++ diff --git a/CustomIos/android/app/src/main/java/co/uk/customandroid/app/MainApplication.java b/CustomIos/android/app/src/main/java/co/uk/customandroid/app/MainApplication.java + index bc3a9310..f3e0d155 100644 +- --- a/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java +- +++ b/RnDiffApp/android/app/src/main/java/com/rndiffapp/MainApplication.java ++ --- a/CustomIos/android/app/src/main/java/co/uk/customandroid/app/MainApplication.java ++ +++ b/CustomIos/android/app/src/main/java/co/uk/customandroid/app/MainApplication.java +" +`; diff --git a/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade.test.ts.snap b/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade.test.ts.snap index 720da1dbd..5fa67ec34 100644 --- a/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade.test.ts.snap +++ b/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade.test.ts.snap @@ -1,6 +1,88 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`fetches regular patch, adds remote, applies patch, installs deps, removes remote,: RnDiffApp is replaced with app name (TestApp and com.testapp) 1`] = ` +exports[`Upgrade tests for react-native repo cleans up if patching fails, 1`] = ` +"info Fetching diff between v0.57.8 and v0.58.4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +info Applying diff... +warn Excluding files that exist in the template, but not in your project: + - .flowconfig +error Excluding files that failed to apply the diff: + - ios/MyApp.xcodeproj/project.pbxproj +Please make sure to check the actual changes after the upgrade command is finished. +You can find them in our Upgrade Helper web app: https://react-native-community.github.io/upgrade-helper/?from=0.57.8&to=0.58.4 +$ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig --exclude=ios/MyApp.xcodeproj/project.pbxproj -p2 --3way --directory= +debug \\"git apply\\" failed. Error output: +error: .flowconfig: does not exist in index +error: ios/MyApp.xcodeproj/project.pbxproj: patch does not apply +error Automatically applying diff failed. We did our best to automatically upgrade as many files as possible +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +error Patch failed to apply for unknown reason. Please fall back to manual way of upgrading +warn After resolving conflicts don't forget to run \\"pod install\\" inside \\"ios\\" directory +info You may find these resources helpful: +• Release notes: https://github.com/facebook/react-native/releases/tag/v0.58.4 +• Manual Upgrade Helper: https://react-native-community.github.io/upgrade-helper/?from=0.57.8&to=0.58.4 +• Git diff: https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs/0.57.8..0.58.4.diff" +`; + +exports[`Upgrade tests for react-native repo fetches empty patch and installs deps 1`] = ` +"info Fetching diff between v0.57.8 and v0.58.4... +info Diff has no changes to apply, proceeding further +info Installing \\"react-native@0.58.4\\" and its peer dependencies... +$ execa npm info react-native@0.58.4 peerDependencies --json +$ yarn add react-native@0.58.4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native repo fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory 1`] = ` +"info Fetching diff between v0.57.8 and v0.58.4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ +info Applying diff... +$ execa git apply tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +info Installing \\"react-native@0.58.4\\" and its peer dependencies... +$ execa npm info react-native@0.58.4 peerDependencies --json +$ yarn add react-native@0.58.4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +info Running \\"git status\\" to check what changed... +$ execa git status +success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native repo fetches regular patch, adds remote, applies patch, installs deps, removes remote, 1`] = ` +"info Fetching diff between v0.57.8 and v0.58.4... +[fs] write tmp-upgrade-rn.patch +$ execa git rev-parse --show-prefix +$ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +info Applying diff... +$ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= +[fs] unlink tmp-upgrade-rn.patch +$ execa git status -s +info Installing \\"react-native@0.58.4\\" and its peer dependencies... +$ execa npm info react-native@0.58.4 peerDependencies --json +$ yarn add react-native@0.58.4 react@16.6.3 +$ execa git add package.json +$ execa git add yarn.lock +$ execa git add package-lock.json +info Installing CocoaPods dependencies (this may take a few minutes) +info Running \\"git status\\" to check what changed... +$ execa git status +success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" +`; + +exports[`Upgrade tests for react-native repo fetches regular patch, adds remote, applies patch, installs deps, removes remote,: RnDiffApp is replaced with app name (TestApp and com.testapp) 1`] = ` "Snapshot Diff: - First value + Second value @@ -42,7 +124,7 @@ exports[`fetches regular patch, adds remote, applies patch, installs deps, remov " `; -exports[`works with --name-ios and --name-android: RnDiffApp is replaced with app name (CustomIos and co.uk.customandroid.app) 1`] = ` +exports[`Upgrade tests for react-native repo works with --name-ios and --name-android: RnDiffApp is replaced with app name (CustomIos and co.uk.customandroid.app) 1`] = ` "Snapshot Diff: - First value + Second value diff --git a/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts b/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts new file mode 100644 index 000000000..1599bdbc1 --- /dev/null +++ b/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts @@ -0,0 +1,252 @@ +import execa from 'execa'; +import path from 'path'; +import fs from 'fs'; +import snapshotDiff from 'snapshot-diff'; +import stripAnsi from 'strip-ansi'; +import upgrade from '../upgrade'; +import {fetch, logger} from '@react-native-community/cli-tools'; +import loadConfig from '../../../tools/config'; +import merge from '../../../tools/merge'; + +jest.mock('https'); +jest.mock('fs'); +jest.mock('path'); +jest.mock('execa'); + +jest.mock('../../../tools/config'); +jest.mock('../../../tools/packageManager', () => ({ + install: (args) => { + mockPushLog('$ yarn add', ...args); + }, +})); +jest.mock('@react-native-community/cli-tools', () => ({ + ...jest.requireActual('@react-native-community/cli-tools'), + fetch: jest.fn(), + logger: { + info: jest.fn((...args) => mockPushLog('info', args)), + error: jest.fn((...args) => mockPushLog('error', args)), + warn: jest.fn((...args) => mockPushLog('warn', args)), + success: jest.fn((...args) => mockPushLog('success', args)), + debug: jest.fn((...args) => mockPushLog('debug', args)), + log: jest.fn((...args) => mockPushLog(args)), + }, +})); + +const mockFetch = (value = '', status = 200) => { + (fetch as jest.Mock).mockImplementation(() => + Promise.resolve({data: value, status}), + ); +}; + +const mockExecaDefault = (command, args) => { + mockPushLog('$', 'execa', command, args); + if (command === 'npm' && args[3] === '--json') { + return Promise.resolve({stdout: '{"react": "16.6.3"}'}); + } + if (command === 'git' && args[0] === 'rev-parse') { + return Promise.resolve({stdout: ''}); + } + return Promise.resolve({stdout: ''}); +}; + +const mockExecaNested = (command, args) => { + mockPushLog('$', 'execa', command, args); + if (command === 'npm' && args[3] === '--json') { + return Promise.resolve({stdout: '{"react": "16.6.3"}'}); + } + if (command === 'git' && args[0] === 'rev-parse') { + return Promise.resolve({stdout: 'NestedApp/'}); + } + return Promise.resolve({stdout: ''}); +}; + +const ctx = loadConfig(); + +const samplePatch = jest + .requireActual('fs') + .readFileSync(path.join(__dirname, './sample.patch'), 'utf8'); + +let logs = []; +const mockPushLog = (...args) => + logs.push(args.map((x) => (Array.isArray(x) ? x.join(' ') : x)).join(' ')); +const flushOutput = () => stripAnsi(logs.join('\n')); + +const setup = () => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + fs.writeFileSync = jest.fn((filename) => mockPushLog('[fs] write', filename)); + fs.unlinkSync = jest.fn((...args) => mockPushLog('[fs] unlink', args)); + logs = []; + ((execa as unknown) as jest.Mock).mockImplementation(mockExecaDefault); + Object.defineProperty(process, 'platform', { + value: 'darwin', + }); +}; + +const teardown = () => { + fs.writeFileSync = jest.requireMock('fs').writeFileSync; + fs.unlinkSync = jest.requireMock('fs').unlinkSync; +}; + +const usesLatestVersionWhenNonePassed = async (repoName) => { + await upgrade.func([], ctx); + expect(execa).toBeCalledWith('npm', ['info', repoName, 'version']); +}; + +const appliesPatchInCwdWhenNested = async (newVersion) => { + mockFetch(samplePatch, 200); + ((execa as unknown) as jest.Mock).mockImplementation(mockExecaNested); + const config = {...ctx, root: '/project/root/NestedApp'}; + await upgrade.func([newVersion], config); + + expect(execa).toBeCalledWith('git', [ + 'apply', + 'tmp-upgrade-rn.patch', + '--exclude=NestedApp/package.json', + '-p2', + '--3way', + '--directory=NestedApp/', + ]); +}; + +const errorsWhenInvalidVersionPassed = async () => { + await upgrade.func(['next'], ctx); + expect(logger.error).toBeCalledWith( + 'Provided version "next" is not allowed. Please pass a valid semver version', + ); +}; + +const errorsWhenOlderVersionPassed = async ( + olderVersion, + lessOlderVersion, + currentVersion, +) => { + await upgrade.func([olderVersion], ctx); + expect(logger.error).toBeCalledWith( + `Trying to upgrade from newer version "${currentVersion}" to older "${olderVersion}"`, + ); + await upgrade.func([lessOlderVersion], ctx); + expect(logger.error).not.toBeCalledWith( + `Trying to upgrade from newer version "${currentVersion}" to older "${lessOlderVersion}"`, + ); +}; + +const warnsWhenDependencyInSemverRange = async (currentVersion) => { + await upgrade.func([currentVersion], ctx); + expect(logger.warn).toBeCalledWith( + `Specified version "${currentVersion}" is already installed in node_modules and it satisfies "^${currentVersion}" semver range. No need to upgrade`, + ); +}; + +const fetchesEmptyPatchAndInstallsDeps = async (newVersion) => { + mockFetch(); + await upgrade.func([newVersion], ctx); + expect(flushOutput()).toMatchSnapshot(); +}; + +const fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemote = async ( + newVersion, +) => { + mockFetch(samplePatch, 200); + await upgrade.func( + [newVersion], + merge(ctx, { + project: { + ios: {projectName: 'TestApp.xcodeproj'}, + android: {packageName: 'com.testapp'}, + }, + }), + ); + expect(flushOutput()).toMatchSnapshot(); + expect( + snapshotDiff( + samplePatch, + (fs.writeFileSync as jest.Mock).mock.calls[0][1], + { + contextLines: 1, + }, + ), + ).toMatchSnapshot( + 'RnDiffApp is replaced with app name (TestApp and com.testapp)', + ); +}; + +const fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemoteNested = async ( + newVersion, +) => { + mockFetch(samplePatch, 200); + ((execa as unknown) as jest.Mock).mockImplementation(mockExecaNested); + const config = {...ctx, root: '/project/root/NestedApp'}; + await upgrade.func([newVersion], config); + expect(flushOutput()).toMatchSnapshot(); +}; + +const cleansUpIfPatchingFails = async (newVersion) => { + mockFetch(samplePatch, 200); + ((execa as unknown) as jest.Mock).mockImplementation((command, args) => { + mockPushLog('$', 'execa', command, args); + if (command === 'npm' && args[3] === '--json') { + return Promise.resolve({ + stdout: '{"react": "16.6.3"}', + }); + } + if (command === 'git' && args[0] === 'apply') { + return Promise.reject({ + code: 1, + stderr: + 'error: .flowconfig: does not exist in index\nerror: ios/MyApp.xcodeproj/project.pbxproj: patch does not apply', + }); + } + if (command === 'git' && args[0] === 'rev-parse') { + return Promise.resolve({stdout: ''}); + } + return Promise.resolve({stdout: ''}); + }); + try { + await upgrade.func([newVersion], ctx); + } catch (error) { + expect(error.message).toBe( + 'Upgrade failed. Please see the messages above for details', + ); + } + expect(flushOutput()).toMatchSnapshot(); +}; + +const worksWithNameIosAndNameAndroid = async (newVersion) => { + mockFetch(samplePatch, 200); + await upgrade.func( + [newVersion], + merge(ctx, { + project: { + ios: {projectName: 'CustomIos.xcodeproj'}, + android: {packageName: 'co.uk.customandroid.app'}, + }, + }), + ); + expect( + snapshotDiff( + samplePatch, + (fs.writeFileSync as jest.Mock).mock.calls[0][1], + { + contextLines: 1, + }, + ), + ).toMatchSnapshot( + 'RnDiffApp is replaced with app name (CustomIos and co.uk.customandroid.app)', + ); +}; + +export default { + setup, + teardown, + usesLatestVersionWhenNonePassed, + appliesPatchInCwdWhenNested, + errorsWhenInvalidVersionPassed, + errorsWhenOlderVersionPassed, + warnsWhenDependencyInSemverRange, + fetchesEmptyPatchAndInstallsDeps, + fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemote, + fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemoteNested, + cleansUpIfPatchingFails, + worksWithNameIosAndNameAndroid, +}; diff --git a/packages/cli/src/commands/upgrade/__tests__/upgrade-tv.test.ts b/packages/cli/src/commands/upgrade/__tests__/upgrade-tv.test.ts new file mode 100644 index 000000000..e8719fa68 --- /dev/null +++ b/packages/cli/src/commands/upgrade/__tests__/upgrade-tv.test.ts @@ -0,0 +1,94 @@ +import UpgradeTestingMethods from './upgrade-testing-methods'; + +jest.mock( + '/project/root/node_modules/react-native/package.json', + () => ({name: 'react-native-tvos', version: '0.62.2-1'}), + {virtual: true}, +); +jest.mock( + '/project/root/package.json', + () => ({ + name: 'TestApp', + dependencies: {'react-native': 'npm:react-native-tvos@^0.62.2-1'}, + }), + {virtual: true}, +); +jest.mock( + '/project/root/NestedApp/node_modules/react-native/package.json', + () => ({name: 'react-native-tvos', version: '0.62.2-1'}), + {virtual: true}, +); +jest.mock( + '/project/root/NestedApp/package.json', + () => ({ + name: 'TestAppNested', + dependencies: {'react-native': 'npm:react-native-tvos@^0.62.2-1'}, + }), + {virtual: true}, +); + +const repoName = 'react-native-tvos'; +const currentVersion = '0.62.2-1'; +const newVersion = '0.64.2-4'; +const olderVersion = '0.60.2-1'; +const lessOlderVersion = '0.63.3-0'; + +describe('Upgrade tests for react-native-tvos repo', () => { + beforeEach(() => { + UpgradeTestingMethods.setup(); + }); + + afterEach(() => { + UpgradeTestingMethods.teardown(); + }); + + test('uses latest version of react-native when none passed', async () => { + await UpgradeTestingMethods.usesLatestVersionWhenNonePassed(repoName); + }, 60000); + + test('applies patch in current working directory when nested', async () => { + await UpgradeTestingMethods.appliesPatchInCwdWhenNested(newVersion); + }); + + test('errors when invalid version passed', async () => { + await UpgradeTestingMethods.errorsWhenInvalidVersionPassed(); + }, 60000); + + test('errors when older version passed', async () => { + await UpgradeTestingMethods.errorsWhenOlderVersionPassed( + olderVersion, + lessOlderVersion, + currentVersion, + ); + }, 60000); + + test('warns when dependency upgrade version is in semver range', async () => { + await UpgradeTestingMethods.warnsWhenDependencyInSemverRange( + currentVersion, + ); + }, 60000); + + test('fetches empty patch and installs deps', async () => { + await UpgradeTestingMethods.fetchesEmptyPatchAndInstallsDeps(newVersion); + }, 60000); + + test('fetches regular patch, adds remote, applies patch, installs deps, removes remote,', async () => { + await UpgradeTestingMethods.fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemote( + newVersion, + ); + }, 60000); + + test('fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory', async () => { + await UpgradeTestingMethods.fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemoteNested( + newVersion, + ); + }, 60000); + + test('cleans up if patching fails,', async () => { + await UpgradeTestingMethods.cleansUpIfPatchingFails(newVersion); + }, 60000); + + test('works with --name-ios and --name-android', async () => { + await UpgradeTestingMethods.worksWithNameIosAndNameAndroid(newVersion); + }, 60000); +}); diff --git a/packages/cli/src/commands/upgrade/__tests__/upgrade.test.ts b/packages/cli/src/commands/upgrade/__tests__/upgrade.test.ts index 6f938cb8d..bf1ba4ac8 100644 --- a/packages/cli/src/commands/upgrade/__tests__/upgrade.test.ts +++ b/packages/cli/src/commands/upgrade/__tests__/upgrade.test.ts @@ -1,17 +1,5 @@ -import execa from 'execa'; -import path from 'path'; -import fs from 'fs'; -import snapshotDiff from 'snapshot-diff'; -import stripAnsi from 'strip-ansi'; -import upgrade from '../upgrade'; -import {fetch, logger} from '@react-native-community/cli-tools'; -import loadConfig from '../../../tools/config'; -import merge from '../../../tools/merge'; +import UpgradeTestingMethods from './upgrade-testing-methods'; -jest.mock('https'); -jest.mock('fs'); -jest.mock('path'); -jest.mock('execa'); jest.mock( '/project/root/node_modules/react-native/package.json', () => ({name: 'react-native', version: '0.57.8'}), @@ -35,291 +23,69 @@ jest.mock( }), {virtual: true}, ); -jest.mock('../../../tools/config'); -jest.mock('../../../tools/packageManager', () => ({ - install: (args) => { - mockPushLog('$ yarn add', ...args); - }, -})); -jest.mock('@react-native-community/cli-tools', () => ({ - ...jest.requireActual('@react-native-community/cli-tools'), - fetch: jest.fn(), - logger: { - info: jest.fn((...args) => mockPushLog('info', args)), - error: jest.fn((...args) => mockPushLog('error', args)), - warn: jest.fn((...args) => mockPushLog('warn', args)), - success: jest.fn((...args) => mockPushLog('success', args)), - debug: jest.fn((...args) => mockPushLog('debug', args)), - log: jest.fn((...args) => mockPushLog(args)), - }, -})); - -const mockFetch = (value = '', status = 200) => { - (fetch as jest.Mock).mockImplementation(() => - Promise.resolve({data: value, status}), - ); -}; - -const mockExecaDefault = (command, args) => { - mockPushLog('$', 'execa', command, args); - if (command === 'npm' && args[3] === '--json') { - return Promise.resolve({stdout: '{"react": "16.6.3"}'}); - } - if (command === 'git' && args[0] === 'rev-parse') { - return Promise.resolve({stdout: ''}); - } - return Promise.resolve({stdout: ''}); -}; - -const mockExecaNested = (command, args) => { - mockPushLog('$', 'execa', command, args); - if (command === 'npm' && args[3] === '--json') { - return Promise.resolve({stdout: '{"react": "16.6.3"}'}); - } - if (command === 'git' && args[0] === 'rev-parse') { - return Promise.resolve({stdout: 'NestedApp/'}); - } - return Promise.resolve({stdout: ''}); -}; +const repoName = 'react-native'; const currentVersion = '0.57.8'; const newVersion = '0.58.4'; const olderVersion = '0.56.0'; -const ctx = loadConfig(); +const lessOlderVersion = '0.57.10'; -const samplePatch = jest - .requireActual('fs') - .readFileSync(path.join(__dirname, './sample.patch'), 'utf8'); - -let logs = []; -const mockPushLog = (...args) => - logs.push(args.map((x) => (Array.isArray(x) ? x.join(' ') : x)).join(' ')); -const flushOutput = () => stripAnsi(logs.join('\n')); +describe('Upgrade tests for react-native repo', () => { + beforeEach(() => { + UpgradeTestingMethods.setup(); + }); -beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - fs.writeFileSync = jest.fn((filename) => mockPushLog('[fs] write', filename)); - fs.unlinkSync = jest.fn((...args) => mockPushLog('[fs] unlink', args)); - logs = []; - ((execa as unknown) as jest.Mock).mockImplementation(mockExecaDefault); - Object.defineProperty(process, 'platform', { - value: 'darwin', + afterEach(() => { + UpgradeTestingMethods.teardown(); }); -}); -afterEach(() => { - fs.writeFileSync = jest.requireMock('fs').writeFileSync; - fs.unlinkSync = jest.requireMock('fs').unlinkSync; -}); + test('uses latest version of react-native when none passed', async () => { + await UpgradeTestingMethods.usesLatestVersionWhenNonePassed(repoName); + }, 60000); -test('uses latest version of react-native when none passed', async () => { - await upgrade.func([], ctx); - expect(execa).toBeCalledWith('npm', ['info', 'react-native', 'version']); -}, 60000); + test('applies patch in current working directory when nested', async () => { + await UpgradeTestingMethods.appliesPatchInCwdWhenNested(newVersion); + }); -test('applies patch in current working directory when nested', async () => { - mockFetch(samplePatch, 200); - ((execa as unknown) as jest.Mock).mockImplementation(mockExecaNested); - const config = {...ctx, root: '/project/root/NestedApp'}; - await upgrade.func([newVersion], config); + test('errors when invalid version passed', async () => { + await UpgradeTestingMethods.errorsWhenInvalidVersionPassed(); + }, 60000); - expect(execa).toBeCalledWith('git', [ - 'apply', - 'tmp-upgrade-rn.patch', - '--exclude=NestedApp/package.json', - '-p2', - '--3way', - '--directory=NestedApp/', - ]); -}); - -test('errors when invalid version passed', async () => { - await upgrade.func(['next'], ctx); - expect(logger.error).toBeCalledWith( - 'Provided version "next" is not allowed. Please pass a valid semver version', - ); -}, 60000); + test('errors when older version passed', async () => { + await UpgradeTestingMethods.errorsWhenOlderVersionPassed( + olderVersion, + lessOlderVersion, + currentVersion, + ); + }, 60000); -test('errors when older version passed', async () => { - await upgrade.func([olderVersion], ctx); - expect(logger.error).toBeCalledWith( - `Trying to upgrade from newer version "${currentVersion}" to older "${olderVersion}"`, - ); - await upgrade.func(['0.57.10'], ctx); - expect(logger.error).not.toBeCalledWith( - `Trying to upgrade from newer version "${currentVersion}" to older "0.57.10"`, - ); -}, 60000); + test('warns when dependency upgrade version is in semver range', async () => { + await UpgradeTestingMethods.warnsWhenDependencyInSemverRange( + currentVersion, + ); + }, 60000); -test('warns when dependency upgrade version is in semver range', async () => { - await upgrade.func([currentVersion], ctx); - expect(logger.warn).toBeCalledWith( - `Specified version "${currentVersion}" is already installed in node_modules and it satisfies "^0.57.8" semver range. No need to upgrade`, - ); -}, 60000); + test('fetches empty patch and installs deps', async () => { + await UpgradeTestingMethods.fetchesEmptyPatchAndInstallsDeps(newVersion); + }, 60000); -test('fetches empty patch and installs deps', async () => { - mockFetch(); - await upgrade.func([newVersion], ctx); - expect(flushOutput()).toMatchInlineSnapshot(` - "info Fetching diff between v0.57.8 and v0.58.4... - info Diff has no changes to apply, proceeding further - info Installing \\"react-native@0.58.4\\" and its peer dependencies... - $ execa npm info react-native@0.58.4 peerDependencies --json - $ yarn add react-native@0.58.4 react@16.6.3 - $ execa git add package.json - $ execa git add yarn.lock - $ execa git add package-lock.json - info Installing CocoaPods dependencies (this may take a few minutes) - success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" - `); -}, 60000); + test('fetches regular patch, adds remote, applies patch, installs deps, removes remote,', async () => { + await UpgradeTestingMethods.fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemote( + newVersion, + ); + }, 60000); -test('fetches regular patch, adds remote, applies patch, installs deps, removes remote,', async () => { - mockFetch(samplePatch, 200); - await upgrade.func( - [newVersion], - merge(ctx, { - project: { - ios: {projectName: 'TestApp.xcodeproj'}, - android: {packageName: 'com.testapp'}, - }, - }), - ); - expect(flushOutput()).toMatchInlineSnapshot(` - "info Fetching diff between v0.57.8 and v0.58.4... - [fs] write tmp-upgrade-rn.patch - $ execa git rev-parse --show-prefix - $ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= - info Applying diff... - $ execa git apply tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= - [fs] unlink tmp-upgrade-rn.patch - $ execa git status -s - info Installing \\"react-native@0.58.4\\" and its peer dependencies... - $ execa npm info react-native@0.58.4 peerDependencies --json - $ yarn add react-native@0.58.4 react@16.6.3 - $ execa git add package.json - $ execa git add yarn.lock - $ execa git add package-lock.json - info Installing CocoaPods dependencies (this may take a few minutes) - info Running \\"git status\\" to check what changed... - $ execa git status - success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" - `); - expect( - snapshotDiff( - samplePatch, - (fs.writeFileSync as jest.Mock).mock.calls[0][1], - { - contextLines: 1, - }, - ), - ).toMatchSnapshot( - 'RnDiffApp is replaced with app name (TestApp and com.testapp)', - ); -}, 60000); -test('fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory', async () => { - mockFetch(samplePatch, 200); - ((execa as unknown) as jest.Mock).mockImplementation(mockExecaNested); - const config = {...ctx, root: '/project/root/NestedApp'}; - await upgrade.func([newVersion], config); - expect(flushOutput()).toMatchInlineSnapshot(` - "info Fetching diff between v0.57.8 and v0.58.4... - [fs] write tmp-upgrade-rn.patch - $ execa git rev-parse --show-prefix - $ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ - info Applying diff... - $ execa git apply tmp-upgrade-rn.patch --exclude=NestedApp/package.json -p2 --3way --directory=NestedApp/ - [fs] unlink tmp-upgrade-rn.patch - $ execa git status -s - info Installing \\"react-native@0.58.4\\" and its peer dependencies... - $ execa npm info react-native@0.58.4 peerDependencies --json - $ yarn add react-native@0.58.4 react@16.6.3 - $ execa git add package.json - $ execa git add yarn.lock - $ execa git add package-lock.json - info Installing CocoaPods dependencies (this may take a few minutes) - info Running \\"git status\\" to check what changed... - $ execa git status - success Upgraded React Native to v0.58.4 🎉. Now you can review and commit the changes" - `); -}, 60000); -test('cleans up if patching fails,', async () => { - mockFetch(samplePatch, 200); - ((execa as unknown) as jest.Mock).mockImplementation((command, args) => { - mockPushLog('$', 'execa', command, args); - if (command === 'npm' && args[3] === '--json') { - return Promise.resolve({ - stdout: '{"react": "16.6.3"}', - }); - } - if (command === 'git' && args[0] === 'apply') { - return Promise.reject({ - code: 1, - stderr: - 'error: .flowconfig: does not exist in index\nerror: ios/MyApp.xcodeproj/project.pbxproj: patch does not apply', - }); - } - if (command === 'git' && args[0] === 'rev-parse') { - return Promise.resolve({stdout: ''}); - } - return Promise.resolve({stdout: ''}); - }); - try { - await upgrade.func([newVersion], ctx); - } catch (error) { - expect(error.message).toBe( - 'Upgrade failed. Please see the messages above for details', + test('fetches regular patch, adds remote, applies patch, installs deps, removes remote when updated from nested directory', async () => { + await UpgradeTestingMethods.fetchesRegularPatchInstallRemoteAppliesPatchInstallsDepsRemovesRemoteNested( + newVersion, ); - } - expect(flushOutput()).toMatchInlineSnapshot(` - "info Fetching diff between v0.57.8 and v0.58.4... - [fs] write tmp-upgrade-rn.patch - $ execa git rev-parse --show-prefix - $ execa git apply --binary --check tmp-upgrade-rn.patch --exclude=package.json -p2 --3way --directory= - info Applying diff... - warn Excluding files that exist in the template, but not in your project: - - .flowconfig - error Excluding files that failed to apply the diff: - - ios/MyApp.xcodeproj/project.pbxproj - Please make sure to check the actual changes after the upgrade command is finished. - You can find them in our Upgrade Helper web app: https://react-native-community.github.io/upgrade-helper/?from=0.57.8&to=0.58.4 - $ execa git apply tmp-upgrade-rn.patch --exclude=package.json --exclude=.flowconfig --exclude=ios/MyApp.xcodeproj/project.pbxproj -p2 --3way --directory= - debug \\"git apply\\" failed. Error output: - error: .flowconfig: does not exist in index - error: ios/MyApp.xcodeproj/project.pbxproj: patch does not apply - error Automatically applying diff failed. We did our best to automatically upgrade as many files as possible - [fs] unlink tmp-upgrade-rn.patch - $ execa git status -s - error Patch failed to apply for unknown reason. Please fall back to manual way of upgrading - warn After resolving conflicts don't forget to run \\"pod install\\" inside \\"ios\\" directory - info You may find these resources helpful: - • Release notes: https://github.com/facebook/react-native/releases/tag/v0.58.4 - • Manual Upgrade Helper: https://react-native-community.github.io/upgrade-helper/?from=0.57.8&to=0.58.4 - • Git diff: https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs/0.57.8..0.58.4.diff" - `); -}, 60000); -test('works with --name-ios and --name-android', async () => { - mockFetch(samplePatch, 200); - await upgrade.func( - [newVersion], - merge(ctx, { - project: { - ios: {projectName: 'CustomIos.xcodeproj'}, - android: {packageName: 'co.uk.customandroid.app'}, - }, - }), - ); - expect( - snapshotDiff( - samplePatch, - (fs.writeFileSync as jest.Mock).mock.calls[0][1], - { - contextLines: 1, - }, - ), - ).toMatchSnapshot( - 'RnDiffApp is replaced with app name (CustomIos and co.uk.customandroid.app)', - ); -}, 60000); + }, 60000); + + test('cleans up if patching fails,', async () => { + await UpgradeTestingMethods.cleansUpIfPatchingFails(newVersion); + }, 60000); + + test('works with --name-ios and --name-android', async () => { + await UpgradeTestingMethods.worksWithNameIosAndNameAndroid(newVersion); + }, 60000); +}); diff --git a/packages/cli/src/commands/upgrade/upgrade.ts b/packages/cli/src/commands/upgrade/upgrade.ts index c1c07d0b7..66a1f510b 100644 --- a/packages/cli/src/commands/upgrade/upgrade.ts +++ b/packages/cli/src/commands/upgrade/upgrade.ts @@ -8,10 +8,26 @@ import {logger, CLIError, fetch} from '@react-native-community/cli-tools'; import * as PackageManager from '../../tools/packageManager'; import installPods from '../../tools/installPods'; +type UpgradeError = {message: string; stderr: string}; + // https://react-native-community.github.io/upgrade-helper/?from=0.59.10&to=0.60.0-rc.3 -const webDiffUrl = 'https://react-native-community.github.io/upgrade-helper'; -const rawDiffUrl = - 'https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs'; + +type RepoNameType = 'react-native' | 'react-native-tvos'; + +const repos = { + 'react-native': { + rawDiffUrl: + 'https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs', + webDiffUrl: 'https://react-native-community.github.io/upgrade-helper', + dependencyName: 'react-native', + }, + 'react-native-tvos': { + rawDiffUrl: + 'https://raw.githubusercontent.com/react-native-tvos/rn-diff-purge-tv/diffs/diffs', + webDiffUrl: 'https://react-native-community.github.io/upgrade-helper', + dependencyName: 'react-native@npm:react-native-tvos', + }, +}; const isConnected = (output: string): boolean => { // there is no reliable way of checking for internet connectivity, so we should just @@ -38,23 +54,20 @@ const checkForErrors = (output: string): void => { } }; -const getLatestRNVersion = async (): Promise => { +const getLatestRNVersion = async (repoName: RepoNameType): Promise => { logger.info('No version passed. Fetching latest...'); - const {stdout, stderr} = await execa('npm', [ - 'info', - 'react-native', - 'version', - ]); + const {stdout, stderr} = await execa('npm', ['info', repoName, 'version']); checkForErrors(stderr); return stdout; }; const getRNPeerDeps = async ( version: string, + repoName: RepoNameType, ): Promise<{[key: string]: string}> => { const {stdout, stderr} = await execa('npm', [ 'info', - `react-native@${version}`, + `${repoName}@${version}`, 'peerDependencies', '--json', ]); @@ -66,6 +79,7 @@ const getPatch = async ( currentVersion: string, newVersion: string, config: Config, + repoName: RepoNameType, ) => { let patch; @@ -73,12 +87,12 @@ const getPatch = async ( try { const {data} = await fetch( - `${rawDiffUrl}/${currentVersion}..${newVersion}.diff`, + `${repos[repoName].rawDiffUrl}/${currentVersion}..${newVersion}.diff`, ); patch = data; } catch (error) { - logger.error(error.message); + logger.error((error as UpgradeError).message); logger.error( `Failed to fetch diff for react-native@${newVersion}. Maybe it's not released yet?`, ); @@ -125,13 +139,14 @@ const getVersionToUpgradeTo = async ( argv: Array, currentVersion: string, projectDir: string, + repoName: RepoNameType, ) => { const argVersion = argv[0]; const semverCoercedVersion = semver.coerce(argVersion); const newVersion = argVersion ? semver.valid(argVersion) || (semverCoercedVersion ? semverCoercedVersion.version : null) - : await getLatestRNVersion(); + : await getLatestRNVersion(repoName); if (!newVersion) { logger.error( @@ -151,14 +166,16 @@ const getVersionToUpgradeTo = async ( dependencies: {'react-native': version}, } = require(path.join(projectDir, 'package.json')); - if (semver.satisfies(newVersion, version)) { + const parsedVersion = version.split('@')[version.split('@').length - 1]; + + if (semver.satisfies(newVersion, parsedVersion)) { logger.warn( - `Specified version "${newVersion}" is already installed in node_modules and it satisfies "${version}" semver range. No need to upgrade`, + `Specified version "${newVersion}" is already installed in node_modules and it satisfies "${parsedVersion}" semver range. No need to upgrade`, ); return null; } logger.error( - `Dependency mismatch. Specified version "${newVersion}" is already installed in node_modules and it doesn't satisfy "${version}" semver range of your "react-native" dependency. Please re-install your dependencies`, + `Dependency mismatch. Specified version "${newVersion}" is already installed in node_modules and it doesn't satisfy "${parsedVersion}" semver range of your "react-native" dependency. Please re-install your dependencies`, ); return null; } @@ -166,13 +183,17 @@ const getVersionToUpgradeTo = async ( return newVersion; }; -const installDeps = async (root: string, newVersion: string) => { +const installDeps = async ( + root: string, + newVersion: string, + repoName: RepoNameType, +) => { logger.info( `Installing "react-native@${newVersion}" and its peer dependencies...`, ); - const peerDeps = await getRNPeerDeps(newVersion); + const peerDeps = await getRNPeerDeps(newVersion, repoName); const deps = [ - `react-native@${newVersion}`, + `${repos[repoName].dependencyName}@${newVersion}`, ...Object.keys(peerDeps).map((module) => `${module}@${peerDeps[module]}`), ]; await PackageManager.install(deps, { @@ -204,9 +225,11 @@ const installCocoaPodsDeps = async (projectDir: string) => { directory: projectDir.split('/').pop() || '', }); } catch (error) { - if (error.stderr) { + if ((error as UpgradeError).stderr) { logger.debug( - `"pod install" or "pod repo update" failed. Error output:\n${error.stderr}`, + `"pod install" or "pod repo update" failed. Error output:\n${ + (error as UpgradeError).stderr + }`, ); } logger.error( @@ -220,6 +243,7 @@ const applyPatch = async ( currentVersion: string, newVersion: string, tmpPatchFile: string, + repoName: RepoNameType, ) => { const defaultExcludes = ['package.json']; let filesThatDontExist: Array = []; @@ -249,7 +273,9 @@ const applyPatch = async ( ]); logger.info('Applying diff...'); } catch (error) { - const errorLines: Array = error.stderr.split('\n'); + const errorLines: Array = (error as UpgradeError).stderr.split( + '\n', + ); filesThatDontExist = [ ...errorLines .filter((x) => x.includes('does not exist in index')) @@ -276,7 +302,7 @@ const applyPatch = async ( .join( '\n', )}\nPlease make sure to check the actual changes after the upgrade command is finished.\nYou can find them in our Upgrade Helper web app: ${chalk.underline.dim( - `${webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, + `${repos[repoName].webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, )}`, ); } @@ -296,8 +322,10 @@ const applyPatch = async ( ]); } } catch (error) { - if (error.stderr) { - logger.debug(`"git apply" failed. Error output:\n${error.stderr}`); + if ((error as UpgradeError).stderr) { + logger.debug( + `"git apply" failed. Error output:\n${(error as UpgradeError).stderr}`, + ); } logger.error( 'Automatically applying diff failed. We did our best to automatically upgrade as many files as possible', @@ -313,22 +341,26 @@ const applyPatch = async ( async function upgrade(argv: Array, ctx: Config) { const tmpPatchFile = 'tmp-upgrade-rn.patch'; const projectDir = ctx.root; - const {version: currentVersion} = require(path.join( + const {name: rnName, version: currentVersion} = require(path.join( projectDir, 'node_modules/react-native/package.json', )); + const repoName: RepoNameType = + rnName === 'react-native-tvos' ? 'react-native-tvos' : 'react-native'; + const newVersion = await getVersionToUpgradeTo( argv, currentVersion, projectDir, + repoName, ); if (!newVersion) { return; } - const patch = await getPatch(currentVersion, newVersion, ctx); + const patch = await getPatch(currentVersion, newVersion, ctx, repoName); if (patch === null) { return; @@ -336,7 +368,7 @@ async function upgrade(argv: Array, ctx: Config) { if (patch === '') { logger.info('Diff has no changes to apply, proceeding further'); - await installDeps(projectDir, newVersion); + await installDeps(projectDir, newVersion, repoName); await installCocoaPodsDeps(projectDir); logger.success( @@ -348,9 +380,14 @@ async function upgrade(argv: Array, ctx: Config) { try { fs.writeFileSync(tmpPatchFile, patch); - patchSuccess = await applyPatch(currentVersion, newVersion, tmpPatchFile); + patchSuccess = await applyPatch( + currentVersion, + newVersion, + tmpPatchFile, + repoName, + ); } catch (error) { - throw new Error(error.stderr || error); + throw new Error((error as UpgradeError).stderr || (error as string)); } finally { try { fs.unlinkSync(tmpPatchFile); @@ -363,7 +400,7 @@ async function upgrade(argv: Array, ctx: Config) { logger.warn( 'Continuing after failure. Some of the files are upgraded but you will need to deal with conflicts manually', ); - await installDeps(projectDir, newVersion); + await installDeps(projectDir, newVersion, repoName); logger.info('Running "git status" to check what changed...'); await execa('git', ['status'], {stdio: 'inherit'}); } else { @@ -372,7 +409,7 @@ async function upgrade(argv: Array, ctx: Config) { ); } } else { - await installDeps(projectDir, newVersion); + await installDeps(projectDir, newVersion, repoName); await installCocoaPodsDeps(projectDir); logger.info('Running "git status" to check what changed...'); await execa('git', ['status'], {stdio: 'inherit'}); @@ -393,10 +430,10 @@ async function upgrade(argv: Array, ctx: Config) { `https://github.com/facebook/react-native/releases/tag/v${newVersion}`, )} • Manual Upgrade Helper: ${chalk.underline.dim( - `${webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, + `${repos[repoName].webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, )} • Git diff: ${chalk.underline.dim( - `${rawDiffUrl}/${currentVersion}..${newVersion}.diff`, + `${repos[repoName].rawDiffUrl}/${currentVersion}..${newVersion}.diff`, )}`); throw new CLIError(