From a6bdacb2575dcc3be2acec95d8a6db6e2db909c4 Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Thu, 7 Feb 2019 09:23:10 -0800 Subject: [PATCH] Kill react-native-git-upgrade Summary: This tool doesn't work and is a frequent source of pain for people. Nobody maintains it. Let's kill it and stop recommending it to people (PR on the website repo here: https://github.com/facebook/react-native-website/pull/761 ). Reviewed By: TheSavior Differential Revision: D13987587 fbshipit-source-id: d04d31d3ecba4b9f9387ecb86355b5a5d08485c4 --- react-native-git-upgrade/README.md | 17 - react-native-git-upgrade/checks.js | 67 ---- react-native-git-upgrade/cli.js | 24 -- react-native-git-upgrade/cliEntry.js | 466 -------------------------- react-native-git-upgrade/index.js | 44 --- react-native-git-upgrade/package.json | 30 -- react-native-git-upgrade/yarn.js | 62 ---- 7 files changed, 710 deletions(-) delete mode 100644 react-native-git-upgrade/README.md delete mode 100644 react-native-git-upgrade/checks.js delete mode 100644 react-native-git-upgrade/cli.js delete mode 100644 react-native-git-upgrade/cliEntry.js delete mode 100644 react-native-git-upgrade/index.js delete mode 100644 react-native-git-upgrade/package.json delete mode 100644 react-native-git-upgrade/yarn.js diff --git a/react-native-git-upgrade/README.md b/react-native-git-upgrade/README.md deleted file mode 100644 index 4b7930c93d7118..00000000000000 --- a/react-native-git-upgrade/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# React Native Git Upgrade - -This tool makes upgrading your apps to a new version of React Native easier than the stock `react-native upgrade` command. - -It uses Git under the hood to automatically resolve merge conflicts in project templates (native iOS and Android files, `.flowconfig` etc.). These conflicts happen when a new React Native version introduces changes to those files and you have local changes in those files too, which is quite common. - -## Usage - -See the [Upgrading docs](https://facebook.github.io/react-native/docs/upgrading.html) on the React Native website. - -Basic usage: - -``` -$ npm install -g react-native-git-upgrade -$ cd MyReactNativeApp -$ react-native-git-upgrade -``` diff --git a/react-native-git-upgrade/checks.js b/react-native-git-upgrade/checks.js deleted file mode 100644 index 615648b98418d7..00000000000000 --- a/react-native-git-upgrade/checks.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const {execSync} = require('child_process'); -const semver = require('semver'); - -function checkDeclaredVersion(declaredVersion) { - if (!declaredVersion) { - throw new Error( - 'Your "package.json" file doesn\'t seem to have "react-native" as a dependency.', - ); - } -} - -function checkMatchingVersions(currentVersion, declaredVersion, useYarn) { - if (!semver.satisfies(currentVersion, declaredVersion)) { - throw new Error( - 'react-native version in "package.json" (' + - declaredVersion + - ") doesn't match " + - 'the installed version in "node_modules" (' + - currentVersion + - ').\n' + - (useYarn - ? 'Try running "yarn" to fix this.' - : 'Try running "npm install" to fix this.'), - ); - } -} - -function checkReactPeerDependency(currentVersion, declaredReactVersion) { - if (semver.lt(currentVersion, '0.21.0') && !declaredReactVersion) { - throw new Error( - 'Your "package.json" file doesn\'t seem to have "react" as a dependency.\n' + - '"react" was changed from a dependency to a peer dependency in react-native v0.21.0.\n' + - 'Therefore, it\'s necessary to include "react" in your project\'s dependencies.\n' + - 'Please run "npm install --save react", then re-run ' + - '"react-native upgrade".', - ); - } -} - -function checkGitAvailable() { - try { - execSync('git --version'); - } catch (error) { - throw new Error( - '"react-native-git-upgrade" requires "git" to be available in path. ' + - 'Please install Git (https://git-scm.com)"', - ); - } -} - -module.exports = { - checkDeclaredVersion, - checkMatchingVersions, - checkReactPeerDependency, - checkGitAvailable, -}; diff --git a/react-native-git-upgrade/cli.js b/react-native-git-upgrade/cli.js deleted file mode 100644 index 1de6d61266168f..00000000000000 --- a/react-native-git-upgrade/cli.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -require('babel-register')({ - babelrc: false, - presets: [ - require('babel-preset-es2015-node'), - require('babel-preset-stage-3'), - ], - // Enable transpiling for react-native-git-upgrade AND the generator, just like the upgrade CLI command does - only: /(react-native-git-upgrade\/(?!(node_modules)))/, -}); - -var cliEntry = require('./cliEntry'); - -module.exports = cliEntry; diff --git a/react-native-git-upgrade/cliEntry.js b/react-native-git-upgrade/cliEntry.js deleted file mode 100644 index 71ff6139dcc94e..00000000000000 --- a/react-native-git-upgrade/cliEntry.js +++ /dev/null @@ -1,466 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const fs = require('fs'); -const os = require('os'); -const assert = require('assert'); -const path = require('path'); -const shell = require('shelljs'); -const Promise = require('promise'); -const yeoman = require('yeoman-environment'); -const TerminalAdapter = require('yeoman-environment/lib/adapter'); -const log = require('npmlog'); -const rimraf = require('rimraf'); -const semver = require('semver'); -const yarn = require('./yarn'); - -const { - checkDeclaredVersion, - checkMatchingVersions, - checkReactPeerDependency, - checkGitAvailable, -} = require('./checks'); - -log.heading = 'git-upgrade'; - -/** - * Promisify the callback-based shelljs function exec - * @param logOutput If true, log the stdout of the command. - * @param logger Custom logger to modify the output, invoked with the data and the stream. - * @returns {Promise} - */ -function exec(command, logOutput, logger = null) { - return new Promise((resolve, reject) => { - let stderr, - stdout = ''; - const child = shell.exec(command, {async: true, silent: true}); - - child.stdout.on('data', data => { - stdout += data; - if (logOutput) { - if (logger) { - logger(data, process.stdout); - } else { - process.stdout.write(data); - } - } - }); - - child.stderr.on('data', data => { - stderr += data; - if (logger) { - logger(data, process.stderr); - } else { - process.stderr.write(data); - } - }); - - child.on('exit', (code, signal) => { - if (code === 0) { - resolve(stdout); - } else if (code) { - reject( - new Error(`Command '${command}' exited with code ${code}: -stderr: ${stderr} -stdout: ${stdout}`), - ); - } else { - reject( - new Error(`Command '${command}' terminated with signal '${signal}': -stderr: ${stderr} -stdout: ${stdout}`), - ); - } - }); - }); -} - -function parseJsonFile(filePath, useYarn) { - const installHint = useYarn - ? 'Make sure you ran "yarn" and that you are inside a React Native project.' - : 'Make sure you ran "npm install" and that you are inside a React Native project.'; - let fileContents; - try { - fileContents = fs.readFileSync(filePath, 'utf8'); - } catch (err) { - throw new Error('Cannot find "' + filePath + '". ' + installHint); - } - try { - return JSON.parse(fileContents); - } catch (err) { - throw new Error('Cannot parse "' + filePath + '": ' + err.message); - } -} - -function readPackageFiles(useYarn) { - const reactNativeNodeModulesPakPath = path.resolve( - process.cwd(), - 'node_modules', - 'react-native', - 'package.json', - ); - const reactNodeModulesPakPath = path.resolve( - process.cwd(), - 'node_modules', - 'react', - 'package.json', - ); - const pakPath = path.resolve(process.cwd(), 'package.json'); - const appPath = path.resolve(process.cwd(), 'app.json'); - let app = null; - try { - app = parseJsonFile(appPath); - } catch (err) { - log.warn('Unable to parse app.json', err.message); - } - return { - reactNativeNodeModulesPak: parseJsonFile(reactNativeNodeModulesPakPath), - reactNodeModulesPak: parseJsonFile(reactNodeModulesPakPath), - pak: parseJsonFile(pakPath), - app: app, - }; -} - -function parseInformationJsonOutput(jsonOutput, requestedVersion) { - try { - const output = JSON.parse(jsonOutput); - const newVersion = output.version; - const peerDependencies = output.peerDependencies; - const newReactVersionRange = peerDependencies.react; - - assert(semver.valid(newVersion)); - - return {newVersion, newReactVersionRange}; - } catch (err) { - throw new Error( - 'The specified version of React Native ' + - requestedVersion + - " doesn't exist.\n" + - 'Re-run the react-native-git-upgrade command with an existing version,\n' + - 'for example: "react-native-git-upgrade 0.38.0",\n' + - 'or without arguments to upgrade to the latest: "react-native-git-upgrade".', - ); - } -} - -function setupWorkingDir(tmpDir) { - return new Promise((resolve, reject) => { - rimraf(tmpDir, err => { - if (err) { - reject(err); - } else { - fs.mkdirSync(tmpDir); - resolve(); - } - }); - }); -} - -function configureGitEnv(tmpDir) { - /* - * The workflow inits a temporary Git repository. We don't want to interfere - * with an existing user's Git repository. - * Thanks to Git env vars, we can make Git use a different directory for its ".git" folder. - * See https://git-scm.com/book/tr/v2/Git-Internals-Environment-Variables - */ - process.env.GIT_DIR = path.resolve(tmpDir, '.gitrn'); - process.env.GIT_WORK_TREE = '.'; -} - -function copyCurrentGitIgnoreFile(tmpDir) { - /* - * The user may have added new files or directories in the .gitignore file. - * We need to keep those files ignored during the process, otherwise they - * will be deleted. - * See https://github.com/facebook/react-native/issues/12237 - */ - try { - const gitignorePath = path.resolve(process.cwd(), '.gitignore'); - const repoExcludePath = path.resolve( - tmpDir, - process.env.GIT_DIR, - 'info/exclude', - ); - const content = fs.readFileSync(gitignorePath, 'utf8'); - fs.appendFileSync(repoExcludePath, content); - } catch (err) { - if (err.code === 'ENOENT') { - log.info('No .gitignore file found, this step is a no-op'); - return; - } - - throw err; - } -} - -function generateTemplates(generatorDir, appName, verbose) { - try { - const yeomanGeneratorEntryPoint = path.resolve(generatorDir, 'index.js'); - // Try requiring the index.js (entry-point of Yeoman generators) - fs.accessSync(yeomanGeneratorEntryPoint); - return runYeomanGenerators(generatorDir, appName, verbose); - } catch (err) { - return runCopyAndReplace(generatorDir, appName); - } -} - -function runCopyAndReplace(generatorDir, appName) { - const copyProjectTemplateAndReplacePath = path.resolve( - generatorDir, - 'copyProjectTemplateAndReplace', - ); - /* - * This module is required twice during the process: for both old and new version - * of React Native. - * This file could have changed between these 2 versions. When generating the new template, - * we don't want to load the old version of the generator from the cache - */ - delete require.cache[require.resolve(copyProjectTemplateAndReplacePath)]; - const copyProjectTemplateAndReplace = require(copyProjectTemplateAndReplacePath); - copyProjectTemplateAndReplace( - path.resolve(generatorDir, '..', 'templates', 'HelloWorld'), - process.cwd(), - appName, - {upgrade: true, force: true}, - ); -} - -function runYeomanGenerators(generatorDir, appName, verbose) { - if (!verbose) { - // Yeoman output needs monkey-patching (no silent option) - TerminalAdapter.prototype.log = () => {}; - TerminalAdapter.prototype.log.force = () => {}; - TerminalAdapter.prototype.log.create = () => {}; - } - - const env = yeoman.createEnv(); - env.register(generatorDir, 'react:app'); - const generatorArgs = ['react:app', appName]; - return new Promise(resolve => - env.run(generatorArgs, {upgrade: true, force: true}, resolve), - ); -} - -/** - * If there's a newer version of react-native-git-upgrade in npm, suggest to the user to upgrade. - */ -async function checkForUpdates() { - try { - log.info('Check for updates'); - const lastGitUpgradeVersion = await exec( - 'npm view react-native-git-upgrade@latest version', - ); - const current = require('./package').version; - const latest = semver.clean(lastGitUpgradeVersion); - if (semver.gt(latest, current)) { - log.warn( - 'A more recent version of "react-native-git-upgrade" has been found.\n' + - `Current: ${current}\n` + - `Latest: ${latest}\n` + - 'Please run "npm install -g react-native-git-upgrade"', - ); - } - } catch (err) { - log.warn('Check for latest version failed', err.message); - } -} - -/** - * If true, use yarn instead of the npm client to upgrade the project. - */ -function shouldUseYarn(cliArgs, projectDir) { - if (cliArgs && cliArgs.npm) { - return false; - } - const yarnVersion = yarn.getYarnVersionIfAvailable(); - if (yarnVersion && yarn.isProjectUsingYarn(projectDir)) { - log.info('Using yarn ' + yarnVersion); - return true; - } - return false; -} - -/** - * @param requestedVersion The version argument, e.g. 'react-native-git-upgrade 0.38'. - * `undefined` if no argument passed. - * @param cliArgs Additional arguments parsed using minimist. - */ -async function run(requestedVersion, cliArgs) { - const tmpDir = path.resolve(os.tmpdir(), 'react-native-git-upgrade'); - const generatorDir = path.resolve( - process.cwd(), - 'node_modules', - '@react-native-community/cli', - 'generator', - ); - let projectBackupCreated = false; - - try { - await checkForUpdates(); - - const useYarn = shouldUseYarn(cliArgs, path.resolve(process.cwd())); - - log.info('Read package.json files'); - const { - reactNativeNodeModulesPak, - reactNodeModulesPak, - pak, - app, - } = readPackageFiles(useYarn); - const appName = (app && app.name) || pak.name; - const currentVersion = reactNativeNodeModulesPak.version; - const currentReactVersion = reactNodeModulesPak.version; - const declaredVersion = pak.dependencies['react-native']; - const declaredReactVersion = pak.dependencies.react; - - const verbose = cliArgs.verbose; - - log.info('Check declared version'); - checkDeclaredVersion(declaredVersion); - - log.info('Check matching versions'); - checkMatchingVersions(currentVersion, declaredVersion, useYarn); - - log.info('Check React peer dependency'); - checkReactPeerDependency(currentVersion, declaredReactVersion); - - log.info('Check that Git is installed'); - checkGitAvailable(); - - log.info('Get information from NPM registry'); - const viewCommand = - 'npm view react-native@' + (requestedVersion || 'latest') + ' --json'; - const jsonOutput = await exec(viewCommand, verbose); - const {newVersion, newReactVersionRange} = parseInformationJsonOutput( - jsonOutput, - requestedVersion, - ); - // Print which versions we're upgrading to - log.info( - 'Upgrading to React Native ' + - newVersion + - (newReactVersionRange ? ', React ' + newReactVersionRange : ''), - ); - - log.info('Setup temporary working directory'); - await setupWorkingDir(tmpDir); - - log.info('Configure Git environment'); - configureGitEnv(tmpDir); - - log.info('Init temporary Git repository'); - await exec('git init', verbose); - - log.info('Save current .gitignore file'); - copyCurrentGitIgnoreFile(tmpDir); - - log.info('Add all files to commit'); - await exec('git add .', verbose); - - log.info('Commit current project sources'); - await exec('git commit -m "Project snapshot" --no-verify', verbose); - - log.info('Create a tag before updating sources'); - await exec('git tag project-snapshot', verbose); - projectBackupCreated = true; - - log.info('Generate old version template'); - await generateTemplates(generatorDir, appName, verbose); - - log.info('Add updated files to commit'); - await exec('git add .', verbose); - - log.info('Commit old version template'); - await exec( - 'git commit -m "Old version" --allow-empty --no-verify', - verbose, - ); - - log.info('Install the new version'); - let installCommand; - if (useYarn) { - installCommand = 'yarn add'; - } else { - installCommand = 'npm install --save --color=always'; - } - installCommand += ' react-native@' + newVersion; - if ( - newReactVersionRange && - !semver.satisfies(currentReactVersion, newReactVersionRange) - ) { - // Install React as well to avoid unmet peer dependency - installCommand += ' react@' + newReactVersionRange; - } - await exec(installCommand, verbose); - - log.info('Generate new version template'); - await generateTemplates(generatorDir, appName, verbose); - - log.info('Add updated files to commit'); - await exec('git add .', verbose); - - log.info('Commit new version template'); - await exec( - 'git commit -m "New version" --allow-empty --no-verify', - verbose, - ); - - log.info('Generate the patch between the 2 versions'); - const diffOutput = await exec( - 'git diff --binary --no-color HEAD~1 HEAD', - verbose, - ); - - log.info('Save the patch in temp directory'); - const patchPath = path.resolve( - tmpDir, - `upgrade_${currentVersion}_${newVersion}.patch`, - ); - fs.writeFileSync(patchPath, diffOutput); - - log.info('Reset the 2 temporary commits'); - await exec('git reset HEAD~2 --hard', verbose); - - try { - log.info('Apply the patch'); - await exec(`git apply --3way ${patchPath}`, true, (data, stream) => { - if (data.indexOf('conflicts') >= 0 || data.startsWith('U ')) { - stream.write(`\x1b[31m${data}\x1b[0m`); - } else { - stream.write(data); - } - }); - } catch (err) { - log.warn( - 'The upgrade process succeeded but there might be conflicts to be resolved. ' + - 'See above for the list of files that have merge conflicts. ' + - 'If you don’t see the expected changes, try running:\n' + - `git apply --reject ${patchPath}`, - ); - } finally { - log.info('Upgrade done'); - if (cliArgs.verbose) { - log.info(`Temporary working directory: ${tmpDir}`); - } - } - } catch (err) { - log.error('An error occurred during upgrade:'); - log.error(err.stack); - if (projectBackupCreated) { - log.error('Restore initial sources'); - await exec('git checkout project-snapshot --no-verify', true); - } - } -} - -module.exports = { - run: run, -}; diff --git a/react-native-git-upgrade/index.js b/react-native-git-upgrade/index.js deleted file mode 100644 index 235a3b4ddca826..00000000000000 --- a/react-native-git-upgrade/index.js +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -var argv = require('minimist')(process.argv.slice(2)); -var cli = require('./cli'); - -if (argv._.length === 0 && (argv.h || argv.help)) { - console.log( - [ - '', - ' Usage: react-native-git-upgrade [version] [options]', - '', - '', - ' Commands:', - '', - ' [Version] upgrades React Native and app templates to the desired version', - ' (latest, if not specified)', - '', - ' Options:', - '', - ' -h, --help output usage information', - ' -v, --version output the version number', - ' --verbose output debugging info', - ' --npm force using the npm client even if your project uses yarn', - '', - ].join('\n'), - ); - process.exit(0); -} - -if (argv._.length === 0 && (argv.v || argv.version)) { - console.log(require('./package.json').version); - process.exit(0); -} - -cli.run(argv._[0], argv).catch(console.error); diff --git a/react-native-git-upgrade/package.json b/react-native-git-upgrade/package.json deleted file mode 100644 index 21cfa896188626..00000000000000 --- a/react-native-git-upgrade/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "react-native-git-upgrade", - "version": "0.2.7", - "license": "MIT", - "description": "The React Native upgrade tool", - "main": "cli.js", - "bin": { - "react-native-git-upgrade": "index.js" - }, - "engines": { - "node": ">=4" - }, - "repository": { - "type": "git", - "url": "https://github.com/facebook/react-native.git" - }, - "dependencies": { - "babel-core": "^6.18.0", - "babel-preset-es2015-node": "^6.1.1", - "babel-preset-stage-3": "^6.17.0", - "babel-register": "^6.18.0", - "minimist": "^1.2.0", - "npmlog": "^4.0.0", - "promise": "^7.1.1", - "rimraf": "^2.5.4", - "semver": "^5.0.3", - "shelljs": "^0.7.5", - "yeoman-environment": "1.5.3" - } -} diff --git a/react-native-git-upgrade/yarn.js b/react-native-git-upgrade/yarn.js deleted file mode 100644 index 1fea500cefb203..00000000000000 --- a/react-native-git-upgrade/yarn.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const execSync = require('child_process').execSync; -const fs = require('fs'); -const path = require('path'); -const semver = require('semver'); - -/** - * Use Yarn if available, it's much faster than the npm client. - * Return the version of yarn installed on the system, null if yarn is not available. - */ -function getYarnVersionIfAvailable() { - let yarnVersion; - try { - // execSync returns a Buffer -> convert to string - if (process.platform.startsWith('win')) { - yarnVersion = (execSync('yarn --version').toString() || '').trim(); - } else { - yarnVersion = ( - execSync('yarn --version 2>/dev/null').toString() || '' - ).trim(); - } - } catch (error) { - return null; - } - // yarn < 0.16 has a 'missing manifest' bug - try { - if (semver.gte(yarnVersion, '0.16.0')) { - return yarnVersion; - } else { - return null; - } - } catch (error) { - console.error('Cannot parse yarn version: ' + yarnVersion); - return null; - } -} - -/** - * Check that 'react-native init' itself used yarn to install React Native. - * When using an old global react-native-cli@1.0.0 (or older), we don't want - * to install React Native with npm, and React + Jest with yarn. - * Let's be safe and not mix yarn and npm in a single project. - * @param projectDir e.g. /Users/martin/AwesomeApp - */ -function isProjectUsingYarn(projectDir) { - return fs.existsSync(path.join(projectDir, 'yarn.lock')); -} - -module.exports = { - getYarnVersionIfAvailable: getYarnVersionIfAvailable, - isProjectUsingYarn: isProjectUsingYarn, -};