UNPKG

react-native

Version:

A framework for building native apps using React

273 lines (243 loc) 8.49 kB
#!/usr/bin/env node /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format */ 'use strict'; /*:: import type {IncomingMessage} from 'https'; */ // $FlowFixMe[untyped-import] const {name, version: currentVersion} = require('./package.json'); const {spawn} = require('child_process'); const {get} = require('https'); const semver = require('semver'); const {URL} = require('url'); const {styleText} = require('util'); const deprecated = () => { throw new Error( 'react-native/cli is deprecated, please use @react-native-community/cli instead', ); }; function findCommunityCli(startDir /*: string */ = process.cwd()) { // With isolated node_modules (eg pnpm), we won't be able to find // `@react-native-community/cli` starting from the `react-native` directory. // Instead, we should use the project root, which we assume to be the cwd. const options /*: {paths?: Array<string>, ...} */ = {paths: [startDir]}; const rncli = require.resolve('@react-native-community/cli', options); // $FlowFixMe[unsupported-syntax] return require(rncli); } function isMissingCliDependency(error /*: Error */) { return ( // $FlowFixMe[prop-missing] error.code === 'MODULE_NOT_FOUND' && /@react-native-community\/cli/.test(error.message) ); } let cli /*: $ReadOnly<{ bin: string, loadConfig: $FlowFixMe, run: () => void }> */ = { bin: '/dev/null', loadConfig: deprecated, run: deprecated, }; const isNpxRuntime = process.env.npm_lifecycle_event === 'npx'; const isInitCommand = process.argv[2] === 'init'; const DEFAULT_REGISTRY_HOST = process.env.npm_config_registry ?? 'https://registry.npmjs.org/'; const HEAD = '1000.0.0'; // We're going to deprecate the `init` command proxying requests to @react-native-community/cli transparently // on December 31th, 2024 or 0.76 (whichever arrives first). This is part of work to decouple of community CLI from React Native core. // // See https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0759-react-native-frameworks.md const CLI_DEPRECATION_DATE = new Date('2024-12-31'); function getLatestVersion( registryHost /*: string */ = DEFAULT_REGISTRY_HOST, ) /*: Promise<string> */ { return new Promise((resolve, reject) => { const url = new URL(registryHost); url.pathname = 'react-native/latest'; get(url.toString(), (resp /*: IncomingMessage */) => { const buffer = []; resp.on('data', data => buffer.push(data)); resp.on('end', () => { try { resolve(JSON.parse(Buffer.concat(buffer).toString('utf8')).version); } catch (e) { reject(e); } }); }).on('error', e => reject(e)); }); } /** * Warn when users are using `npx react-native init`, to raise awareness of the changes from RFC 0759. * * Phase 1 * * @see https://github.com/react-native-community/discussions-and-proposals/tree/main/proposals/0759-react-native-frameworks.md */ function warnWhenRunningInit() { if (isInitCommand) { console.warn( `\nRunning: ${styleText(['grey', 'bold'], 'npx @react-native-community/cli init')}\n`, ); } } /** * Warn more sternly that the ability to call `npx react-native init` is going away. * * Phase 2 * * @see https://github.com/react-native-community/discussions-and-proposals/tree/main/proposals/0759-react-native-frameworks.md */ function warnWithDeprecationSchedule() { if (!isInitCommand) { return; } const daysRemaining = Math.ceil( (CLI_DEPRECATION_DATE.getTime() - new Date().getTime()) / 86_400_000, ); const emphasis = (text /*: string */) => daysRemaining < 10 ? styleText(['bgRed', 'white', 'bold'], text) : daysRemaining < 30 ? styleText(['red', 'bold'], text) : daysRemaining < 60 ? styleText(['green', 'bold'], text) : styleText(['blueBright', 'bold'], text); console.warn(` ${styleText('yellow', '⚠️')} The \`init\` command is deprecated. The behavior will be changed on ${styleText(['white', 'bold'], CLI_DEPRECATION_DATE.toLocaleDateString())} ${emphasis(`(${daysRemaining} day${daysRemaining > 1 ? 's' : ''})`)}. - Switch to ${styleText(['grey', 'bold'], 'npx @react-native-community/cli init')} for the identical behavior. - Refer to the documentation for information about alternative tools: ${styleText('dim', 'https://reactnative.dev/docs/getting-started')}`); } function warnWithDeprecated() { if (!isInitCommand) { return; } console.warn(` 🚨️ The \`init\` command is deprecated. - Switch to ${styleText(['grey', 'bold'], 'npx @react-native-community/cli init')} for the identical behavior. - Refer to the documentation for information about alternative tools: ${styleText('dim', 'https://reactnative.dev/docs/getting-started')}`); } function warnWithExplicitDependency(version /*: string */ = '*') { console.warn(` ${styleText('yellow', '⚠')}${styleText(['dim'], 'react-native')} depends on ${styleText('dim', '@react-native-community/cli')} for cli commands. To fix update your ${styleText(['dim'], 'package.json')} to include: ${styleText( ['white', 'bold'], ` "devDependencies": { "@react-native-community/cli": "latest", } `, )} `); } /** * npx react-native -> @react-native-community/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 && !Boolean(process.env.SKIP) && currentVersion !== HEAD && isInitCommand ) { try { const latest = await getLatestVersion(); // TODO: T184416093 When cli is deprecated, remove semver from package.json if (semver.lt(currentVersion, latest)) { const msg = ` ${styleText(['bold', 'yellow'], 'WARNING:')} You should run ${styleText( ['white', 'bold'], 'npx react-native@latest', )} to ensure you're always using the most current version of the CLI. NPX has cached version (${styleText( ['bold', 'yellow'], currentVersion, )}) != current release (${styleText(['bold', 'green'], latest)}) `; console.warn(msg); } } catch (_) { // Ignore errors, since it's a nice to have warning } } const isDeprecated = CLI_DEPRECATION_DATE.getTime() <= new Date().getTime() || currentVersion.startsWith('0.77'); /** * This command is now deprecated. We will continue to proxy commands to @react-native-community/cli, but it * isn't supported anymore. We'll always show the warning. * * WARNING: Projects will have to have an explicit dependency on @react-native-community/cli to use the CLI. * * Phase 3 * * @see https://github.com/react-native-community/discussions-and-proposals/tree/main/proposals/0759-react-native-frameworks.md */ if (isInitCommand) { if (currentVersion !== HEAD && isDeprecated) { warnWithDeprecated(); // We only exit if the user calls `init` and it's deprecated. All other cases should proxy to to @react-native-community/cli. // Be careful with this as it can break a lot of users. console.warn(`${styleText('green', 'Exiting...')}`); process.exit(1); } else if ( currentVersion.startsWith('0.75') || currentVersion.startsWith('0.76') ) { // We check deprecation schedule only for 0.75 and 0.76 and 0.77 is expected to land in Jan 2025. warnWithDeprecationSchedule(); } warnWhenRunningInit(); const proc = spawn( 'npx', ['@react-native-community/cli', ...process.argv.slice(2)], { stdio: 'inherit', }, ); const code /*: number */ = await new Promise(resolve => { proc.on('exit', resolve); }); process.exit(code); } try { return findCommunityCli().run(name); } catch (e) { if (isMissingCliDependency(e)) { warnWithExplicitDependency(); process.exit(1); } throw e; } } if (require.main === module) { void main(); } else { try { cli = findCommunityCli(); } catch (e) { // We silence @react-native-community/cli missing as it is no // longer a dependency if (!isMissingCliDependency(e)) { throw e; } } } module.exports = cli;