diff --git a/.changeset/afraid-adults-hear.md b/.changeset/afraid-adults-hear.md new file mode 100644 index 000000000..36a697286 --- /dev/null +++ b/.changeset/afraid-adults-hear.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/cli": patch +--- + +Resolve correct `@react-native-community/cli-clean` instance through `react-native` diff --git a/.changeset/fluffy-dots-kick.md b/.changeset/fluffy-dots-kick.md new file mode 100644 index 000000000..f568953e5 --- /dev/null +++ b/.changeset/fluffy-dots-kick.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/tools-node": minor +--- + +Added function for resolving a dependency chain diff --git a/.changeset/proud-kiwis-heal.md b/.changeset/proud-kiwis-heal.md new file mode 100644 index 000000000..516f4b1d7 --- /dev/null +++ b/.changeset/proud-kiwis-heal.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/jest-preset": patch +--- + +Resolve correct `@react-native-community/cli` instance through `react-native` diff --git a/.changeset/tricky-jokes-explain.md b/.changeset/tricky-jokes-explain.md new file mode 100644 index 000000000..1c9252f5d --- /dev/null +++ b/.changeset/tricky-jokes-explain.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/metro-service": patch +--- + +Resolve correct `@react-native-community/cli-plugin-metro` instance through `react-native` diff --git a/packages/cli/package.json b/packages/cli/package.json index 8b6b38e1d..8bff2b099 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,22 +41,10 @@ "readline": "^1.3.0" }, "peerDependencies": { - "@react-native-community/cli": ">=5.0.1", - "@react-native-community/cli-clean": ">=8.0.0", - "@react-native-community/cli-server-api": ">=5.0.1", "jest-cli": ">=26.0", "react-native": ">=0.64" }, "peerDependenciesMeta": { - "@react-native-community/cli": { - "optional": true - }, - "@react-native-community/cli-clean": { - "optional": true - }, - "@react-native-community/cli-server-api": { - "optional": true - }, "jest-cli": { "optional": true }, diff --git a/packages/cli/src/clean.ts b/packages/cli/src/clean.ts index 717decd91..69e6427f1 100644 --- a/packages/cli/src/clean.ts +++ b/packages/cli/src/clean.ts @@ -4,6 +4,7 @@ import * as fs from "fs-extra"; import ora from "ora"; import * as os from "os"; import * as path from "path"; +import { requireExternal } from "./serve/external"; type Args = { include?: string; @@ -30,7 +31,7 @@ export async function rnxClean( const spinner = ora(); try { - require.resolve("@react-native-community/cli-clean"); + requireExternal("@react-native-community/cli-clean"); spinner.warn( "`rnx-clean` has been upstreamed to `@react-native-community/cli`. Please use `npx react-native clean` instead." ); diff --git a/packages/cli/src/serve/external.ts b/packages/cli/src/serve/external.ts index 7f182dfc4..c4ad8d047 100644 --- a/packages/cli/src/serve/external.ts +++ b/packages/cli/src/serve/external.ts @@ -1,14 +1,16 @@ +import { resolveDependencyChain } from "@rnx-kit/tools-node/package"; import type { CliServerApi, CoreDevMiddleware } from "./types"; +type CliClean = typeof import("@react-native-community/cli-clean"); + type ExternalModule = + | "@react-native-community/cli-clean" | "@react-native-community/cli-server-api" | "@react-native/dev-middleware"; function friendlyRequire(...modules: string[]): T { try { - const modulePath = modules.reduce((startDir, module) => { - return require.resolve(module, { paths: [startDir] }); - }, process.cwd()); + const modulePath = resolveDependencyChain(modules); return require(modulePath) as T; } catch (_) { const module = modules[modules.length - 1]; @@ -22,6 +24,10 @@ function friendlyRequire(...modules: string[]): T { } } +export function requireExternal( + module: "@react-native-community/cli-clean" +): CliClean; + export function requireExternal( module: "@react-native-community/cli-server-api" ): CliServerApi; @@ -32,8 +38,14 @@ export function requireExternal( export function requireExternal( module: ExternalModule -): CliServerApi | CoreDevMiddleware { +): CliClean | CliServerApi | CoreDevMiddleware { switch (module) { + case "@react-native-community/cli-clean": + return friendlyRequire( + "react-native", + "@react-native-community/cli", + "@react-native-community/cli-clean" + ); case "@react-native-community/cli-server-api": return friendlyRequire( "react-native", diff --git a/packages/jest-preset/package.json b/packages/jest-preset/package.json index a2088800f..b158f99b8 100644 --- a/packages/jest-preset/package.json +++ b/packages/jest-preset/package.json @@ -23,17 +23,14 @@ "dependencies": { "@babel/preset-env": "^7.0.0", "@babel/preset-typescript": "^7.0.0", + "@rnx-kit/tools-node": "^2.0.1", "find-up": "^5.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0", - "@react-native-community/cli": ">=4.10", "react-native": "^0.0.0-0 || >=0.63" }, "peerDependenciesMeta": { - "@react-native-community/cli": { - "optional": true - }, "react-native": { "optional": true } diff --git a/packages/jest-preset/src/index.js b/packages/jest-preset/src/index.js index 7cab52bc9..b6c876f28 100644 --- a/packages/jest-preset/src/index.js +++ b/packages/jest-preset/src/index.js @@ -77,12 +77,19 @@ function getTargetPlatform(defaultPlatform) { return getReactNativePlatformPath(); } + const { resolveDependencyChain } = require("@rnx-kit/tools-node/package"); + /** @type {() => CLIConfig} */ - const loadConfig = - // @ts-ignore could not find a declaration file - require("@react-native-community/cli").loadConfig || - // @ts-ignore could not find a declaration file - require("@react-native-community/cli/build/tools/config").default; + const loadConfig = (() => { + const rnCliPath = resolveDependencyChain([ + "react-native", + "@react-native-community/cli", + ]); + return ( + require(rnCliPath).loadConfig || + require(`${rnCliPath}/build/tools/config`).default + ); + })(); const { platforms } = loadConfig(); const targetPlatformConfig = platforms[defaultPlatform]; @@ -95,13 +102,12 @@ function getTargetPlatform(defaultPlatform) { // `npmPackageName` is unset if target platform is in core. const { npmPackageName } = targetPlatformConfig; + const projectRoot = { paths: [process.cwd()] }; return [ defaultPlatform, npmPackageName ? path.dirname( - require.resolve(`${npmPackageName}/package.json`, { - paths: [process.cwd()], - }) + require.resolve(`${npmPackageName}/package.json`, projectRoot) ) : undefined, ]; diff --git a/packages/metro-service/package.json b/packages/metro-service/package.json index cce6441c6..24e314a0b 100644 --- a/packages/metro-service/package.json +++ b/packages/metro-service/package.json @@ -27,6 +27,7 @@ "dependencies": { "@rnx-kit/console": "^1.0.0", "@rnx-kit/tools-language": "^2.0.0", + "@rnx-kit/tools-node": "^2.0.1", "chalk": "^4.1.0", "node-fetch": "^2.6.7" }, diff --git a/packages/metro-service/src/config.ts b/packages/metro-service/src/config.ts index 4872a698c..462a1fb82 100644 --- a/packages/metro-service/src/config.ts +++ b/packages/metro-service/src/config.ts @@ -1,4 +1,5 @@ import type { Config as CLIConfig } from "@react-native-community/cli-types"; +import { resolveDependencyChain } from "@rnx-kit/tools-node/package"; import type { ConfigT, InputConfigT } from "metro-config"; import { loadConfig } from "metro-config"; import type { @@ -168,9 +169,13 @@ function getDefaultConfigProvider( } try { - const cliPluginMetro = require.resolve( - "@react-native-community/cli-plugin-metro", - options + const cliPluginMetro = resolveDependencyChain( + [ + "react-native", + "@react-native-community/cli", + "@react-native-community/cli-plugin-metro", + ], + projectRoot ); const { getDefaultConfig } = require(cliPluginMetro); diff --git a/packages/tools-node/README.md b/packages/tools-node/README.md index f37b92751..845b21601 100644 --- a/packages/tools-node/README.md +++ b/packages/tools-node/README.md @@ -41,6 +41,7 @@ import * as pathTools from "@rnx-kit/tools-node/path"; | package | `findPackageDir(startDir)` | Find the parent directory of the nearest `package.json` manifest file. Search upward through all parent directories. | | package | `parsePackageRef(r)` | Parse a package reference string. An example reference is the `name` property found in `package.json`. | | package | `readPackage(pkgPath)` | Read a `package.json` manifest from a file. | +| package | `resolveDependencyChain(chain, startDir)` | Resolve the path to a dependency given a chain of dependencies leading up to it. | | package | `writePackage(pkgPath, manifest, space)` | Write a `package.json` manifest to a file. | | path | `normalizePath(p)` | Normalize the separators in a path, converting each backslash ('\\') to a foreward slash ('/'). | diff --git a/packages/tools-node/src/index.ts b/packages/tools-node/src/index.ts index 3231b3a61..e385d3124 100644 --- a/packages/tools-node/src/index.ts +++ b/packages/tools-node/src/index.ts @@ -12,6 +12,7 @@ export { findPackageDir, parsePackageRef, readPackage, + resolveDependencyChain, writePackage, } from "./package"; export type { diff --git a/packages/tools-node/src/package.ts b/packages/tools-node/src/package.ts index 572d55163..a5830cded 100644 --- a/packages/tools-node/src/package.ts +++ b/packages/tools-node/src/package.ts @@ -206,3 +206,20 @@ export function findPackageDependencyDir( ? path.resolve(path.dirname(packageDir), fs.readlinkSync(packageDir)) : packageDir; } + +/** + * Resolve the path to a dependency given a chain of dependencies leading up to + * it. + * @param chain Chain of dependencies leading up to the target dependency. + * @param startDir Optional starting directory for the search. If not given, the current directory is used. + * @returns Path to the final dependency's directory. + */ +export function resolveDependencyChain( + chain: string[], + startDir = process.cwd() +) { + return chain.reduce((startDir, module) => { + const p = require.resolve(`${module}/package.json`, { paths: [startDir] }); + return path.dirname(p); + }, startDir); +} diff --git a/yarn.lock b/yarn.lock index 8002d6f51..cb2388760 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3511,18 +3511,9 @@ __metadata: type-fest: ^4.0.0 typescript: ^5.0.0 peerDependencies: - "@react-native-community/cli": ">=5.0.1" - "@react-native-community/cli-clean": ">=8.0.0" - "@react-native-community/cli-server-api": ">=5.0.1" jest-cli: ">=26.0" react-native: ">=0.64" peerDependenciesMeta: - "@react-native-community/cli": - optional: true - "@react-native-community/cli-clean": - optional: true - "@react-native-community/cli-server-api": - optional: true jest-cli: optional: true react-native: @@ -3666,6 +3657,7 @@ __metadata: "@jest/types": ^29.2.1 "@rnx-kit/eslint-config": "*" "@rnx-kit/scripts": "*" + "@rnx-kit/tools-node": ^2.0.1 "@types/node": ^18.0.0 eslint: ^8.0.0 find-up: ^5.0.0 @@ -3676,11 +3668,8 @@ __metadata: typescript: ^5.0.0 peerDependencies: "@babel/core": ^7.0.0-0 - "@react-native-community/cli": ">=4.10" react-native: ^0.0.0-0 || >=0.63 peerDependenciesMeta: - "@react-native-community/cli": - optional: true react-native: optional: true languageName: unknown @@ -3861,6 +3850,7 @@ __metadata: "@rnx-kit/console": ^1.0.0 "@rnx-kit/scripts": "*" "@rnx-kit/tools-language": ^2.0.0 + "@rnx-kit/tools-node": ^2.0.1 "@types/node": ^18.0.0 "@types/node-fetch": ^2.6.5 chalk: ^4.1.0