diff --git a/packages/cli-doctor/src/commands/__tests__/info.test.ts b/packages/cli-doctor/src/commands/__tests__/info.test.ts index 03ac69083..3c3d761f4 100644 --- a/packages/cli-doctor/src/commands/__tests__/info.test.ts +++ b/packages/cli-doctor/src/commands/__tests__/info.test.ts @@ -2,7 +2,13 @@ import info from '../info'; import {logger} from '@react-native-community/cli-tools'; import loadConfig from '@react-native-community/cli-config'; -jest.mock('@react-native-community/cli-config'); +jest.mock('@react-native-community/cli-config', () => ({ + __esModule: true, + default: () => ({ + project: {}, + root: '.', + }), +})); beforeEach(() => { jest.resetAllMocks(); diff --git a/packages/cli-tools/src/releaseChecker/getLatestRelease.ts b/packages/cli-tools/src/releaseChecker/getLatestRelease.ts index c1dda32a3..d1eecc533 100644 --- a/packages/cli-tools/src/releaseChecker/getLatestRelease.ts +++ b/packages/cli-tools/src/releaseChecker/getLatestRelease.ts @@ -4,11 +4,38 @@ import {fetch} from '../fetch'; import logger from '../logger'; export type Release = { - version: string; + // The current stable release + stable: string; + // The current candidate release. These are only populated if the latest release is a candidate release. + candidate?: string; changelogUrl: string; diffUrl: string; }; +interface DiffPurge { + name: string; + zipball_url: string; + tarball_url: string; + commit: { + sha: string; + url: string; + }; + node_id: string; +} + +function isDiffPurgeEntry(data: any): data is DiffPurge { + return ( + [ + data.name, + data.zipball_url, + data.tarball_url, + data.commit?.sha, + data.commit?.url, + data.node_id, + ].indexOf(false) === -1 + ); +} + /** * Checks via GitHub API if there is a newer stable React Native release and, * if it exists, returns the release data. @@ -49,17 +76,15 @@ export default async function getLatestRelease( logger.debug('Checking for newer releases on GitHub'); const eTag = cacheManager.get(name, 'eTag'); - const latestVersion = await getLatestRnDiffPurgeVersion(name, eTag); - logger.debug(`Latest release: ${latestVersion}`); + const {stable, candidate} = await getLatestRnDiffPurgeVersion(name, eTag); + logger.debug(`Latest release: ${stable} (${candidate})`); - if ( - semver.compare(latestVersion, currentVersion) === 1 && - !semver.prerelease(latestVersion) - ) { + if (semver.compare(stable, currentVersion) >= 0) { return { - version: latestVersion, - changelogUrl: buildChangelogUrl(latestVersion), - diffUrl: buildDiffUrl(currentVersion), + stable, + candidate, + changelogUrl: buildChangelogUrl(stable), + diffUrl: buildDiffUrl(stable), }; } } catch (e) { @@ -79,13 +104,18 @@ function buildDiffUrl(version: string) { return `https://react-native-community.github.io/upgrade-helper/?from=${version}`; } +type LatestVersions = { + candidate?: string; + stable: string; +}; + /** * Returns the most recent React Native version available to upgrade to. */ async function getLatestRnDiffPurgeVersion( name: string, eTag?: string, -): Promise { +): Promise { const options = { // https://developer.github.com/v3/#user-agent-required headers: {'User-Agent': 'React-Native-CLI'} as Headers, @@ -100,32 +130,38 @@ async function getLatestRnDiffPurgeVersion( options, ); + const result: LatestVersions = {stable: '0.0.0'}; + // Remote is newer. if (status === 200) { - const body: Array = data; - const latestVersion = body[0].name.substring(8); + const body: DiffPurge[] = data.filter(isDiffPurgeEntry); const eTagHeader = headers.get('eTag'); - // Update cache only if newer release is stable. - if (!semver.prerelease(latestVersion) && eTagHeader) { - logger.debug(`Saving ${eTagHeader} to cache`); - cacheManager.set(name, 'eTag', eTagHeader); - cacheManager.set(name, 'latestVersion', latestVersion); + for (let {name: version} of body) { + if (!result.candidate && version.includes('-rc')) { + result.candidate = version.substring(8); + continue; + } + if (!version.includes('-rc')) { + result.stable = version.substring(8); + if (eTagHeader) { + logger.debug(`Saving ${eTagHeader} to cache`); + cacheManager.set(name, 'eTag', eTagHeader); + cacheManager.set(name, 'latestVersion', result.stable); + } + return result; + } } - - return latestVersion; + return result; } // Cache is still valid. if (status === 304) { - const latestVersion = cacheManager.get(name, 'latestVersion'); - if (latestVersion) { - return latestVersion; - } + result.stable = cacheManager.get(name, 'latestVersion') ?? result.stable; } // Should be returned only if something went wrong. - return '0.0.0'; + return result; } type Headers = { diff --git a/packages/cli-tools/src/releaseChecker/index.ts b/packages/cli-tools/src/releaseChecker/index.ts index 2da3a53ae..a9544e762 100644 --- a/packages/cli-tools/src/releaseChecker/index.ts +++ b/packages/cli-tools/src/releaseChecker/index.ts @@ -17,15 +17,21 @@ const getReactNativeVersion = (projectRoot: string): string | undefined => * Logs out a message if the user's version is behind a stable version of React Native */ export async function logIfUpdateAvailable(projectRoot: string): Promise { - const hasUpdate = await latest(projectRoot); - if (hasUpdate) { - printNewRelease(hasUpdate.name, hasUpdate.upgrade, hasUpdate.current); + const versions = await latest(projectRoot); + if (!versions?.upgrade) { + return; + } + if (semver.gt(versions.upgrade.stable, versions.current)) { + printNewRelease(versions.name, versions.upgrade, versions.current); } } type Update = { - upgrade: Release; + // Only populated if an upgrade is available + upgrade?: Release; + // The project's package's current version current: string; + // The project's package's name name: string; }; @@ -39,13 +45,13 @@ export async function latest(projectRoot: string): Promise { return; } const {name} = require(path.join(projectRoot, 'package.json')); - const latestRelease = await getLatestRelease(name, currentVersion); + const upgrade = await getLatestRelease(name, currentVersion); - if (latestRelease) { + if (upgrade) { return { name, current: currentVersion, - upgrade: latestRelease, + upgrade, }; } } catch (e) { @@ -57,7 +63,7 @@ export async function latest(projectRoot: string): Promise { ); logger.debug(e as any); } - return undefined; + return; } /** diff --git a/packages/cli-tools/src/releaseChecker/printNewRelease.ts b/packages/cli-tools/src/releaseChecker/printNewRelease.ts index db1a0821e..60f558041 100644 --- a/packages/cli-tools/src/releaseChecker/printNewRelease.ts +++ b/packages/cli-tools/src/releaseChecker/printNewRelease.ts @@ -15,7 +15,7 @@ export default function printNewRelease( currentVersion: string, ) { logger.info( - `React Native v${latestRelease.version} is now available (your project is running on v${currentVersion}).`, + `React Native v${latestRelease.stable} is now available (your project is running on v${currentVersion}).`, ); logger.info(`Changelog: ${chalk.dim.underline(latestRelease.changelogUrl)}`); logger.info(`Diff: ${chalk.dim.underline(latestRelease.diffUrl)}`); diff --git a/packages/cli/src/commands/upgrade/__mocks__/@react-native-community/cli-config.ts b/packages/cli/src/commands/upgrade/__mocks__/@react-native-community/cli-config.ts deleted file mode 100644 index b106d977a..000000000 --- a/packages/cli/src/commands/upgrade/__mocks__/@react-native-community/cli-config.ts +++ /dev/null @@ -1,17 +0,0 @@ -export default function mockedLoadConfig() { - return { - root: '/project/root', - reactNativePath: '', - commands: [], - platforms: { - ios: {projectConfig: () => null, dependencyConfig: () => null}, - android: {projectConfig: () => null, dependencyConfig: () => null}, - }, - project: { - ios: null, - android: null, - }, - dependencies: {}, - assets: [], - }; -} 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 deleted file mode 100644 index b4b070d2e..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade-tv.test.ts.snap +++ /dev/null @@ -1,167 +0,0 @@ -// 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 deleted file mode 100644 index 5fa67ec34..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/__snapshots__/upgrade.test.ts.snap +++ /dev/null @@ -1,167 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -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 - -@@ -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 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__/sample.patch b/packages/cli/src/commands/upgrade/__tests__/sample.patch deleted file mode 100644 index 42c5efce4..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/sample.patch +++ /dev/null @@ -1,33 +0,0 @@ -diff --git a/RnDiffApp/android/app/src/main/AndroidManifest.xml b/RnDiffApp/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 -@@ -1,8 +1,7 @@ - -+ package="com.rndiffapp"> - - -- - - - #import - --@interface AppDelegate : UIResponder -+@interface AppDelegate : UIResponder - - @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 -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 diff --git a/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts b/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts deleted file mode 100644 index 324a56596..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/upgrade-testing-methods.ts +++ /dev/null @@ -1,252 +0,0 @@ -import execa from 'execa'; -import path from 'path'; -import merge from 'deepmerge'; -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 '@react-native-community/cli-config'; - -jest.mock('https'); -jest.mock('fs'); -jest.mock('path'); -jest.mock('execa'); - -jest.mock('@react-native-community/cli-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: {xcodeProject: {name: '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: {xcodeProject: {name: '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 deleted file mode 100644 index e8719fa68..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/upgrade-tv.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -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 deleted file mode 100644 index bf1ba4ac8..000000000 --- a/packages/cli/src/commands/upgrade/__tests__/upgrade.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import UpgradeTestingMethods from './upgrade-testing-methods'; - -jest.mock( - '/project/root/node_modules/react-native/package.json', - () => ({name: 'react-native', version: '0.57.8'}), - {virtual: true}, -); -jest.mock( - '/project/root/package.json', - () => ({name: 'TestApp', dependencies: {'react-native': '^0.57.8'}}), - {virtual: true}, -); -jest.mock( - '/project/root/NestedApp/node_modules/react-native/package.json', - () => ({name: 'react-native', version: '0.57.8'}), - {virtual: true}, -); -jest.mock( - '/project/root/NestedApp/package.json', - () => ({ - name: 'TestAppNested', - dependencies: {'react-native': '^0.57.8'}, - }), - {virtual: true}, -); - -const repoName = 'react-native'; -const currentVersion = '0.57.8'; -const newVersion = '0.58.4'; -const olderVersion = '0.56.0'; -const lessOlderVersion = '0.57.10'; - -describe('Upgrade tests for react-native 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/upgrade.ts b/packages/cli/src/commands/upgrade/upgrade.ts index cb0a1826d..b52324ac5 100644 --- a/packages/cli/src/commands/upgrade/upgrade.ts +++ b/packages/cli/src/commands/upgrade/upgrade.ts @@ -1,455 +1,53 @@ -import path from 'path'; -import fs from 'fs'; import chalk from 'chalk'; -import semver from 'semver'; -import execa from 'execa'; -import {Config} from '@react-native-community/cli-types'; -import {logger, CLIError, fetch} from '@react-native-community/cli-tools'; -import * as PackageManager from '../../tools/packageManager'; -import {installPods} from '@react-native-community/cli-doctor'; - -type UpgradeError = {message: string; stderr: string}; - -// https://react-native-community.github.io/upgrade-helper/?from=0.59.10&to=0.60.0-rc.3 - -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 - // read the output from npm (to check for connectivity errors) which is faster and relatively more reliable. - return !output.includes('the host is inaccessible'); -}; - -const checkForErrors = (output: string): void => { - if (!output) { - return; - } - if (!isConnected(output)) { - throw new CLIError( - 'Upgrade failed. You do not seem to have an internet connection.', - ); - } - - if (output.includes('npm ERR')) { - throw new CLIError(`Upgrade failed with the following errors:\n${output}`); - } - - if (output.includes('npm WARN')) { - logger.warn(output); - } -}; - -const getLatestRNVersion = async (repoName: RepoNameType): Promise => { - logger.info('No version passed. Fetching latest...'); - 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', - `${repoName}@${version}`, - 'peerDependencies', - '--json', - ]); - checkForErrors(stderr); - return JSON.parse(stdout); -}; - -const getPatch = async ( - currentVersion: string, - newVersion: string, - config: Config, - repoName: RepoNameType, -) => { - let patch; - - logger.info(`Fetching diff between v${currentVersion} and v${newVersion}...`); - - try { - const {data} = await fetch( - `${repos[repoName].rawDiffUrl}/${currentVersion}..${newVersion}.diff`, - ); - - patch = data; - } catch (error) { - logger.error((error as UpgradeError).message); - logger.error( - `Failed to fetch diff for react-native@${newVersion}. Maybe it's not released yet?`, - ); - logger.info( - `For available releases to diff see: ${chalk.underline.dim( - 'https://github.com/react-native-community/rn-diff-purge#diff-table-full-table-here', - )}`, - ); - return null; - } - - let patchWithRenamedProjects = patch; - - Object.keys(config.project).forEach((platform) => { - if (!config.project[platform]) { - return; - } - if (platform === 'ios') { - const xcodeProject = config.project.ios!.xcodeProject; - if (xcodeProject) { - patchWithRenamedProjects = patchWithRenamedProjects.replace( - new RegExp('RnDiffApp', 'g'), - xcodeProject.name.replace('.xcodeproj', ''), - ); - } - } else if (platform === 'android') { - patchWithRenamedProjects = patchWithRenamedProjects - .replace( - new RegExp('com\\.rndiffapp', 'g'), - config.project[platform]!.packageName, - ) - .replace( - new RegExp('com\\.rndiffapp'.split('.').join('/'), 'g'), - config.project[platform]!.packageName.split('.').join('/'), - ); - } else { - logger.warn( - `Unsupported platform: "${platform}". \`upgrade\` only supports iOS and Android.`, - ); - } - }); - - return patchWithRenamedProjects; -}; - -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(repoName); - - if (!newVersion) { - logger.error( - `Provided version "${argv[0]}" is not allowed. Please pass a valid semver version`, - ); - return null; - } - - if (semver.gt(currentVersion, newVersion)) { - logger.error( - `Trying to upgrade from newer version "${currentVersion}" to older "${newVersion}"`, - ); - return null; - } - if (semver.eq(currentVersion, newVersion)) { - const { - dependencies: {'react-native': version}, - } = require(path.join(projectDir, 'package.json')); - - 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 "${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 "${parsedVersion}" semver range of your "react-native" dependency. Please re-install your dependencies`, - ); - return null; - } - - return newVersion; -}; - -const installDeps = async ( - root: string, - newVersion: string, - repoName: RepoNameType, -) => { - logger.info( - `Installing "react-native@${newVersion}" and its peer dependencies...`, - ); - const peerDeps = await getRNPeerDeps(newVersion, repoName); - const deps = [ - `${repos[repoName].dependencyName}@${newVersion}`, - ...Object.keys(peerDeps).map((module) => `${module}@${peerDeps[module]}`), - ]; - await PackageManager.install(deps, { - silent: true, - root, - }); - await execa('git', ['add', 'package.json']); - try { - await execa('git', ['add', 'yarn.lock']); - } catch (error) { - // ignore - } - try { - await execa('git', ['add', 'package-lock.json']); - } catch (error) { - // ignore - } -}; - -const installCocoaPodsDeps = async () => { - if (process.platform === 'darwin') { - try { - logger.info( - `Installing CocoaPods dependencies ${chalk.dim( - '(this may take a few minutes)', - )}`, - ); - await installPods(); - } catch (error) { - if ((error as UpgradeError).stderr) { - logger.debug( - `"pod install" or "pod repo update" failed. Error output:\n${ - (error as UpgradeError).stderr - }`, - ); - } - logger.error( - 'Installation of CocoaPods dependencies failed. Try to install them manually by running "pod install" in "ios" directory after finishing upgrade', - ); - } - } -}; - -const applyPatch = async ( - currentVersion: string, - newVersion: string, - tmpPatchFile: string, - repoName: RepoNameType, -) => { - const defaultExcludes = ['package.json']; - let filesThatDontExist: Array = []; - let filesThatFailedToApply: Array = []; - - const {stdout: relativePathFromRoot} = await execa('git', [ - 'rev-parse', - '--show-prefix', - ]); - try { - try { - const excludes = defaultExcludes.map( - (e) => `--exclude=${path.join(relativePathFromRoot, e)}`, - ); - await execa('git', [ - 'apply', - // According to git documentation, `--binary` flag is turned on by - // default. However it's necessary when running `git apply --check` to - // actually accept binary files, maybe a bug in git? - '--binary', - '--check', - tmpPatchFile, - ...excludes, - '-p2', - '--3way', - `--directory=${relativePathFromRoot}`, - ]); - logger.info('Applying diff...'); - } catch (error) { - const errorLines: Array = (error as UpgradeError).stderr.split( - '\n', - ); - filesThatDontExist = [ - ...errorLines - .filter((x) => x.includes('does not exist in index')) - .map((x) => - x.replace(/^error: (.*): does not exist in index$/, '$1'), - ), - ].filter(Boolean); - - filesThatFailedToApply = errorLines - .filter((x) => x.includes('patch does not apply')) - .map((x) => x.replace(/^error: (.*): patch does not apply$/, '$1')) - .filter(Boolean); - - logger.info('Applying diff...'); - logger.warn( - `Excluding files that exist in the template, but not in your project:\n${filesThatDontExist - .map((file) => ` - ${chalk.bold(file)}`) - .join('\n')}`, - ); - if (filesThatFailedToApply.length) { - logger.error( - `Excluding files that failed to apply the diff:\n${filesThatFailedToApply - .map((file) => ` - ${chalk.bold(file)}`) - .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( - `${repos[repoName].webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, - )}`, - ); - } - } finally { - const excludes = [ - ...defaultExcludes, - ...filesThatDontExist, - ...filesThatFailedToApply, - ].map((e) => `--exclude=${path.join(relativePathFromRoot, e)}`); - await execa('git', [ - 'apply', - tmpPatchFile, - ...excludes, - '-p2', - '--3way', - `--directory=${relativePathFromRoot}`, - ]); - } - } catch (error) { - 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', - ); - return false; - } - return true; -}; +import type {Config} from '@react-native-community/cli-types'; +import {logger, version} from '@react-native-community/cli-tools'; /** * Upgrade application to a new version of React Native. */ -async function upgrade(argv: Array, ctx: Config) { - const tmpPatchFile = 'tmp-upgrade-rn.patch'; - const projectDir = ctx.root; - 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, +async function upgrade(_: string[], {root: projectDir}: Config) { + const url = new URL( + 'https://react-native-community.github.io/upgrade-helper', ); - if (!newVersion) { - return; - } - - const patch = await getPatch(currentVersion, newVersion, ctx, repoName); - - if (patch === null) { - return; + const update = await version.latest(projectDir); + if (!update?.current) { + logger.error( + `Cannot figure out your version of React Native, use: ${chalk.dim( + url.toString(), + )}`, + ); + process.exit(1); } - if (patch === '') { - logger.info('Diff has no changes to apply, proceeding further'); - await installDeps(projectDir, newVersion, repoName); - await installCocoaPodsDeps(); + const from = update.current; + const to = update.upgrade?.stable; + if (to === from) { logger.success( - `Upgraded React Native to v${newVersion} 🎉. Now you can review and commit the changes`, + `You are on the most recent stable release of React Native: ${chalk.white( + from, + )} 🎉.`, ); return; } - let patchSuccess; - - try { - fs.writeFileSync(tmpPatchFile, patch); - patchSuccess = await applyPatch( - currentVersion, - newVersion, - tmpPatchFile, - repoName, - ); - } catch (error) { - throw new Error((error as UpgradeError).stderr || (error as string)); - } finally { - try { - fs.unlinkSync(tmpPatchFile); - } catch (e) { - // ignore - } - const {stdout} = await execa('git', ['status', '-s']); - if (!patchSuccess) { - if (stdout) { - logger.warn( - 'Continuing after failure. Some of the files are upgraded but you will need to deal with conflicts manually', - ); - await installDeps(projectDir, newVersion, repoName); - logger.info('Running "git status" to check what changed...'); - await execa('git', ['status'], {stdio: 'inherit'}); - } else { - logger.error( - 'Patch failed to apply for unknown reason. Please fall back to manual way of upgrading', - ); - } - } else { - await installDeps(projectDir, newVersion, repoName); - await installCocoaPodsDeps(); - logger.info('Running "git status" to check what changed...'); - await execa('git', ['status'], {stdio: 'inherit'}); - } - if (!patchSuccess) { - if (stdout) { - logger.warn( - 'Please run "git diff" to review the conflicts and resolve them', - ); - } - if (process.platform === 'darwin') { - logger.warn( - 'After resolving conflicts don\'t forget to run "pod install" inside "ios" directory', - ); - } - logger.info(`You may find these resources helpful: -• Release notes: ${chalk.underline.dim( - `https://github.com/facebook/react-native/releases/tag/v${newVersion}`, - )} -• Manual Upgrade Helper: ${chalk.underline.dim( - `${repos[repoName].webDiffUrl}/?from=${currentVersion}&to=${newVersion}`, - )} -• Git diff: ${chalk.underline.dim( - `${repos[repoName].rawDiffUrl}/${currentVersion}..${newVersion}.diff`, - )}`); - throw new CLIError( - 'Upgrade failed. Please see the messages above for details', - ); - } + url.searchParams.set('from', from); + if (to) { + url.searchParams.set('to', to); } - logger.success( - `Upgraded React Native to v${newVersion} 🎉. Now you can review and commit the changes`, - ); + + logger.log(` +To upgrade React Native please follow the instructions here: + + ${chalk.dim(url.toString())} +`); } + const upgradeCommand = { - name: 'upgrade [version]', - description: - "Upgrade your app's template files to the specified or latest npm version using `rn-diff-purge` project. Only valid semver versions are allowed.", + name: 'upgrade', + description: 'Generate a link to the upgrade helper to help you upgrade', func: upgrade, }; + export default upgradeCommand;