From bfca23a25d098d1b5c599c1611fafa1d17edebec Mon Sep 17 00:00:00 2001 From: Blake Friedman Date: Mon, 22 May 2023 07:41:36 -0700 Subject: [PATCH] feat(cli) check and warn if npx react-native version is using an old cached version (#37510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Currently npx has a variety of caching strategies to avoid having to pull a version of the package from a registry. These are often unexpected to our users, who may fall behind. After looking at a variety of fancy approaches to dealing with this (the high end of which was intelligently forking npx to run `npx react-native@latest `, the best possible tradeoff for time and simplicity was to warn the user when they weren't running the latest release: {F999520817} ### Problem Details On my laptop when you run `npx ` this it eventually calls [libnpmexec](https://github.com/npm/cli/tree/0783cff9653928359a6c68c8fdf30b9fd02130c9/workspaces/libnpmexec), which applies this lookup [algorithm](https://github.com/npm/cli/blob/0783cff9653928359a6c68c8fdf30b9fd02130c9/workspaces/libnpmexec/lib/index.js#L39-L41) for `package@version`: - is package available in local modules (npm root → `~/project/node_modules/`)?. **Importantly it will walk all the way down to `/` looking for `node_modules/`**. - is package available in global modules (npm root -g → `/Users/blakef/.nvm/versions/node/v17.9.0/lib/node_modules`)? - is package available in npx cache (`~/.npm/_npx`)? - is package available in your registry? Download to the npx cache `~/.npm/_npx//` At this point you'll have a cached copy, which then has its bin script run with the arguments you originally provided. ### How this works against React-Native users Users can get their development environment into a **persistent** pickle with a bunch of unintended side-effects of npx / npm exec’s caching model: - **It matters where you run `npx react-native`**, since it’ll default to the version of react-native in a node package's folder. This works well for us in a React Native project, but not when initializing a project outside of a package folder. - **Global and relative node_modules really matter**. If your users runs npx react-native init and they have a version of react-native installed globally, it’ll use that version. - If the user has a `node_modules/react-native` installation anywhere in the directory hierarchy it’ll be used. For example if I run `npx react-native init Foobar` in `/home/blakef/src/example` , npx will look for versions of react-native like this before searching globals or the npx cache: - /home/blakef/src/example/node_modules - /home/blakef/src/node_modules - /home/blakef/node_modules - /home/node_modules - /node_modules **nvm just makes things harder** if your user switches between versions of node it can be hard to determine if they're affected by a globally installed version. Examples include having a `.nvmrc` file in the directory they run the command which transparently switches node version (and globals location). ## Changelog: [General][Added] - Log a warning if npx react-native uses old cached version Pull Request resolved: https://github.com/facebook/react-native/pull/37510 Test Plan: Ran this directly from the project, defining the `npm_lifecycle_event=npx` to mock directly running using `npx`. Reviewed By: Andjeliko Differential Revision: D46069419 Pulled By: blakef fbshipit-source-id: 1c1af7f639c5312760a39a0828b89b7ddf2b5fda --- packages/react-native/cli.js | 62 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/react-native/cli.js b/packages/react-native/cli.js index 609c8498e5c..301a51241fb 100755 --- a/packages/react-native/cli.js +++ b/packages/react-native/cli.js @@ -10,10 +10,68 @@ 'use strict'; -var cli = require('@react-native-community/cli'); +const {get} = require('https'); +const {URL} = require('url'); +const chalk = require('chalk'); +const cli = require('@react-native-community/cli'); + +const {version: currentVersion} = require('./package.json'); + +const isNpxRuntime = process.env.npm_lifecycle_event === 'npx'; +const DEFAULT_REGISTRY_HOST = + process.env.npm_config_registry ?? 'https://registry.npmjs.org/'; +const HEAD = '1000.0.0'; + +async function getLatestVersion(registryHost = DEFAULT_REGISTRY_HOST) { + return new Promise((res, rej) => { + const url = new URL(registryHost); + url.pathname = 'react-native/latest'; + get(url.toString(), resp => { + const buffer = []; + resp.on('data', data => buffer.push(data)); + resp.on('end', () => { + try { + res(JSON.parse(Buffer.concat(buffer).toString('utf8')).version); + } catch (e) { + rej(e); + } + }); + }).on('error', e => rej(e)); + }); +} + +/** + * npx react-native -> @react-native-comminity/cli + * + * Will perform a version check and warning if you're not running the latest community cli when executed using npx. If + * you know what you're doing, you can skip this check: + * + * SKIP=true npx react-native ... + * + */ +async function main() { + if (isNpxRuntime && !process.env.SKIP && currentVersion !== HEAD) { + try { + const latest = await getLatestVersion(); + if (latest !== currentVersion) { + const msg = ` + ${chalk.bold.yellow('WARNING:')} You should run ${chalk.white.bold( + 'npx react-native@latest', + )} to ensure you're always using the most current version of the CLI. NPX has cached version (${chalk.bold.yellow( + currentVersion, + )}) != current release (${chalk.bold.green(latest)}) + `; + console.warn(msg); + } + } catch (_) { + // Ignore errors, since it's a nice to have warning + } + } + return cli.run(); +} if (require.main === module) { - cli.run(); + main(); } module.exports = cli;