upgrade.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. function _path() {
  7. const data = _interopRequireDefault(require("path"));
  8. _path = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _fs() {
  14. const data = _interopRequireDefault(require("fs"));
  15. _fs = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _chalk() {
  21. const data = _interopRequireDefault(require("chalk"));
  22. _chalk = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _semver() {
  28. const data = _interopRequireDefault(require("semver"));
  29. _semver = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _execa() {
  35. const data = _interopRequireDefault(require("execa"));
  36. _execa = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _cliTools() {
  42. const data = require("@react-native-community/cli-tools");
  43. _cliTools = function () {
  44. return data;
  45. };
  46. return data;
  47. }
  48. var PackageManager = _interopRequireWildcard(require("../../tools/packageManager"));
  49. var _installPods = _interopRequireDefault(require("../../tools/installPods"));
  50. function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
  51. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  52. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  53. // https://react-native-community.github.io/upgrade-helper/?from=0.59.10&to=0.60.0-rc.3
  54. const webDiffUrl = 'https://react-native-community.github.io/upgrade-helper';
  55. const rawDiffUrl = 'https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs';
  56. const isConnected = output => {
  57. // there is no reliable way of checking for internet connectivity, so we should just
  58. // read the output from npm (to check for connectivity errors) which is faster and relatively more reliable.
  59. return !output.includes('the host is inaccessible');
  60. };
  61. const checkForErrors = output => {
  62. if (!output) {
  63. return;
  64. }
  65. if (!isConnected(output)) {
  66. throw new (_cliTools().CLIError)('Upgrade failed. You do not seem to have an internet connection.');
  67. }
  68. if (output.includes('npm ERR')) {
  69. throw new (_cliTools().CLIError)(`Upgrade failed with the following errors:\n${output}`);
  70. }
  71. if (output.includes('npm WARN')) {
  72. _cliTools().logger.warn(output);
  73. }
  74. };
  75. const getLatestRNVersion = async () => {
  76. _cliTools().logger.info('No version passed. Fetching latest...');
  77. const {
  78. stdout,
  79. stderr
  80. } = await (0, _execa().default)('npm', ['info', 'react-native', 'version']);
  81. checkForErrors(stderr);
  82. return stdout;
  83. };
  84. const getRNPeerDeps = async version => {
  85. const {
  86. stdout,
  87. stderr
  88. } = await (0, _execa().default)('npm', ['info', `react-native@${version}`, 'peerDependencies', '--json']);
  89. checkForErrors(stderr);
  90. return JSON.parse(stdout);
  91. };
  92. const getPatch = async (currentVersion, newVersion, config) => {
  93. let patch;
  94. _cliTools().logger.info(`Fetching diff between v${currentVersion} and v${newVersion}...`);
  95. try {
  96. const {
  97. data
  98. } = await (0, _cliTools().fetch)(`${rawDiffUrl}/${currentVersion}..${newVersion}.diff`);
  99. patch = data;
  100. } catch (error) {
  101. _cliTools().logger.error(error.message);
  102. _cliTools().logger.error(`Failed to fetch diff for react-native@${newVersion}. Maybe it's not released yet?`);
  103. _cliTools().logger.info(`For available releases to diff see: ${_chalk().default.underline.dim('https://github.com/react-native-community/rn-diff-purge#diff-table-full-table-here')}`);
  104. return null;
  105. }
  106. let patchWithRenamedProjects = patch;
  107. Object.keys(config.project).forEach(platform => {
  108. if (!config.project[platform]) {
  109. return;
  110. }
  111. if (platform === 'ios') {
  112. patchWithRenamedProjects = patchWithRenamedProjects.replace(new RegExp('RnDiffApp', 'g'), config.project[platform].projectName.replace('.xcodeproj', ''));
  113. } else if (platform === 'android') {
  114. 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('/'));
  115. } else {
  116. _cliTools().logger.warn(`Unsupported platform: "${platform}". \`upgrade\` only supports iOS and Android.`);
  117. }
  118. });
  119. return patchWithRenamedProjects;
  120. };
  121. const getVersionToUpgradeTo = async (argv, currentVersion, projectDir) => {
  122. const argVersion = argv[0];
  123. const semverCoercedVersion = _semver().default.coerce(argVersion);
  124. const newVersion = argVersion ? _semver().default.valid(argVersion) || (semverCoercedVersion ? semverCoercedVersion.version : null) : await getLatestRNVersion();
  125. if (!newVersion) {
  126. _cliTools().logger.error(`Provided version "${argv[0]}" is not allowed. Please pass a valid semver version`);
  127. return null;
  128. }
  129. if (_semver().default.gt(currentVersion, newVersion)) {
  130. _cliTools().logger.error(`Trying to upgrade from newer version "${currentVersion}" to older "${newVersion}"`);
  131. return null;
  132. }
  133. if (_semver().default.eq(currentVersion, newVersion)) {
  134. const {
  135. dependencies: {
  136. 'react-native': version
  137. }
  138. } = require(_path().default.join(projectDir, 'package.json'));
  139. if (_semver().default.satisfies(newVersion, version)) {
  140. _cliTools().logger.warn(`Specified version "${newVersion}" is already installed in node_modules and it satisfies "${version}" semver range. No need to upgrade`);
  141. return null;
  142. }
  143. _cliTools().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`);
  144. return null;
  145. }
  146. return newVersion;
  147. };
  148. const installDeps = async (root, newVersion) => {
  149. _cliTools().logger.info(`Installing "react-native@${newVersion}" and its peer dependencies...`);
  150. const peerDeps = await getRNPeerDeps(newVersion);
  151. const deps = [`react-native@${newVersion}`, ...Object.keys(peerDeps).map(module => `${module}@${peerDeps[module]}`)];
  152. await PackageManager.install(deps, {
  153. silent: true,
  154. root
  155. });
  156. await (0, _execa().default)('git', ['add', 'package.json']);
  157. try {
  158. await (0, _execa().default)('git', ['add', 'yarn.lock']);
  159. } catch (error) {// ignore
  160. }
  161. try {
  162. await (0, _execa().default)('git', ['add', 'package-lock.json']);
  163. } catch (error) {// ignore
  164. }
  165. };
  166. const installCocoaPodsDeps = async projectDir => {
  167. if (process.platform === 'darwin') {
  168. try {
  169. _cliTools().logger.info(`Installing CocoaPods dependencies ${_chalk().default.dim('(this may take a few minutes)')}`);
  170. await (0, _installPods.default)({
  171. projectName: projectDir.split('/').pop() || ''
  172. });
  173. } catch (error) {
  174. if (error.stderr) {
  175. _cliTools().logger.debug(`"pod install" or "pod repo update" failed. Error output:\n${error.stderr}`);
  176. }
  177. _cliTools().logger.error('Installation of CocoaPods dependencies failed. Try to install them manually by running "pod install" in "ios" directory after finishing upgrade');
  178. }
  179. }
  180. };
  181. const applyPatch = async (currentVersion, newVersion, tmpPatchFile) => {
  182. const defaultExcludes = ['package.json'];
  183. let filesThatDontExist = [];
  184. let filesThatFailedToApply = [];
  185. const {
  186. stdout: relativePathFromRoot
  187. } = await (0, _execa().default)('git', ['rev-parse', '--show-prefix']);
  188. try {
  189. try {
  190. const excludes = defaultExcludes.map(e => `--exclude=${_path().default.join(relativePathFromRoot, e)}`);
  191. await (0, _execa().default)('git', ['apply', // According to git documentation, `--binary` flag is turned on by
  192. // default. However it's necessary when running `git apply --check` to
  193. // actually accept binary files, maybe a bug in git?
  194. '--binary', '--check', tmpPatchFile, ...excludes, '-p2', '--3way', `--directory=${relativePathFromRoot}`]);
  195. _cliTools().logger.info('Applying diff...');
  196. } catch (error) {
  197. const errorLines = error.stderr.split('\n');
  198. filesThatDontExist = [...errorLines.filter(x => x.includes('does not exist in index')).map(x => x.replace(/^error: (.*): does not exist in index$/, '$1'))].filter(Boolean);
  199. filesThatFailedToApply = errorLines.filter(x => x.includes('patch does not apply')).map(x => x.replace(/^error: (.*): patch does not apply$/, '$1')).filter(Boolean);
  200. _cliTools().logger.info('Applying diff...');
  201. _cliTools().logger.warn(`Excluding files that exist in the template, but not in your project:\n${filesThatDontExist.map(file => ` - ${_chalk().default.bold(file)}`).join('\n')}`);
  202. if (filesThatFailedToApply.length) {
  203. _cliTools().logger.error(`Excluding files that failed to apply the diff:\n${filesThatFailedToApply.map(file => ` - ${_chalk().default.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().default.underline.dim(`${webDiffUrl}/?from=${currentVersion}&to=${newVersion}`)}`);
  204. }
  205. } finally {
  206. const excludes = [...defaultExcludes, ...filesThatDontExist, ...filesThatFailedToApply].map(e => `--exclude=${_path().default.join(relativePathFromRoot, e)}`);
  207. await (0, _execa().default)('git', ['apply', tmpPatchFile, ...excludes, '-p2', '--3way', `--directory=${relativePathFromRoot}`]);
  208. }
  209. } catch (error) {
  210. if (error.stderr) {
  211. _cliTools().logger.debug(`"git apply" failed. Error output:\n${error.stderr}`);
  212. }
  213. _cliTools().logger.error('Automatically applying diff failed. We did our best to automatically upgrade as many files as possible');
  214. return false;
  215. }
  216. return true;
  217. };
  218. /**
  219. * Upgrade application to a new version of React Native.
  220. */
  221. async function upgrade(argv, ctx) {
  222. const tmpPatchFile = 'tmp-upgrade-rn.patch';
  223. const projectDir = ctx.root;
  224. const {
  225. version: currentVersion
  226. } = require(_path().default.join(projectDir, 'node_modules/react-native/package.json'));
  227. const newVersion = await getVersionToUpgradeTo(argv, currentVersion, projectDir);
  228. if (!newVersion) {
  229. return;
  230. }
  231. const patch = await getPatch(currentVersion, newVersion, ctx);
  232. if (patch === null) {
  233. return;
  234. }
  235. if (patch === '') {
  236. _cliTools().logger.info('Diff has no changes to apply, proceeding further');
  237. await installDeps(projectDir, newVersion);
  238. await installCocoaPodsDeps(projectDir);
  239. _cliTools().logger.success(`Upgraded React Native to v${newVersion} 🎉. Now you can review and commit the changes`);
  240. return;
  241. }
  242. let patchSuccess;
  243. try {
  244. _fs().default.writeFileSync(tmpPatchFile, patch);
  245. patchSuccess = await applyPatch(currentVersion, newVersion, tmpPatchFile);
  246. } catch (error) {
  247. throw new Error(error.stderr || error);
  248. } finally {
  249. try {
  250. _fs().default.unlinkSync(tmpPatchFile);
  251. } catch (e) {// ignore
  252. }
  253. const {
  254. stdout
  255. } = await (0, _execa().default)('git', ['status', '-s']);
  256. if (!patchSuccess) {
  257. if (stdout) {
  258. _cliTools().logger.warn('Continuing after failure. Some of the files are upgraded but you will need to deal with conflicts manually');
  259. await installDeps(projectDir, newVersion);
  260. _cliTools().logger.info('Running "git status" to check what changed...');
  261. await (0, _execa().default)('git', ['status'], {
  262. stdio: 'inherit'
  263. });
  264. } else {
  265. _cliTools().logger.error('Patch failed to apply for unknown reason. Please fall back to manual way of upgrading');
  266. }
  267. } else {
  268. await installDeps(projectDir, newVersion);
  269. await installCocoaPodsDeps(projectDir);
  270. _cliTools().logger.info('Running "git status" to check what changed...');
  271. await (0, _execa().default)('git', ['status'], {
  272. stdio: 'inherit'
  273. });
  274. }
  275. if (!patchSuccess) {
  276. if (stdout) {
  277. _cliTools().logger.warn('Please run "git diff" to review the conflicts and resolve them');
  278. }
  279. if (process.platform === 'darwin') {
  280. _cliTools().logger.warn('After resolving conflicts don\'t forget to run "pod install" inside "ios" directory');
  281. }
  282. _cliTools().logger.info(`You may find these resources helpful:
  283. • Release notes: ${_chalk().default.underline.dim(`https://github.com/facebook/react-native/releases/tag/v${newVersion}`)}
  284. • Manual Upgrade Helper: ${_chalk().default.underline.dim(`${webDiffUrl}/?from=${currentVersion}&to=${newVersion}`)}
  285. • Git diff: ${_chalk().default.underline.dim(`${rawDiffUrl}/${currentVersion}..${newVersion}.diff`)}`);
  286. throw new (_cliTools().CLIError)('Upgrade failed. Please see the messages above for details');
  287. }
  288. }
  289. _cliTools().logger.success(`Upgraded React Native to v${newVersion} 🎉. Now you can review and commit the changes`);
  290. }
  291. const upgradeCommand = {
  292. name: 'upgrade [version]',
  293. 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.",
  294. func: upgrade
  295. };
  296. var _default = upgradeCommand;
  297. exports.default = _default;
  298. //# sourceMappingURL=upgrade.js.map