Skip to content

Commit a3a70a1

Browse files
thymikeegrabbou
authored andcommitted
feat: run npx from JS app dir (#824)
* feat: run npx from JS app dir * improve comment * use .sourceFile * extract getCommandOutput * better errors * apply feedback
1 parent 7ead62a commit a3a70a1

File tree

2 files changed

+57
-35
lines changed

2 files changed

+57
-35
lines changed

packages/cli/src/tools/config/resolveReactNativePath.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ export default function resolveReactNativePath(root: string) {
1111
return resolveNodeModuleDir(root, 'react-native');
1212
} catch (_ignored) {
1313
throw new CLIError(`
14-
Unable to find React Native files. Make sure "react-native" module is installed
14+
Unable to find React Native files looking up from ${root}. Make sure "react-native" module is installed
1515
in your project dependencies.
1616
1717
If you are using React Native from a non-standard location, consider setting:
1818
{
19-
"react-native": {
20-
"reactNativePath": "./path/to/react-native"
21-
}
19+
reactNativePath: "./path/to/react-native"
2220
}
23-
in your \`package.json\`.
21+
in your \`react-native.config.js\`.
2422
`);
2523
}
2624
}

packages/platform-android/native_modules.gradle

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import groovy.json.JsonSlurper
22
import org.gradle.initialization.DefaultSettings
33
import org.apache.tools.ant.taskdefs.condition.Os
44

5+
def jsAppDir = buildscript.sourceFile.toString().split("node_modules/@react-native-community/cli-platform-android")[0]
56
def generatedFileName = "PackageList.java"
67
def generatedFilePackage = "com.facebook.react"
78
def generatedFileContentsTemplate = """
@@ -71,12 +72,14 @@ public class PackageList {
7172
class ReactNativeModules {
7273
private Logger logger
7374
private String packageName
75+
private String jsAppDir
7476
private ArrayList<HashMap<String, String>> reactNativeModules
7577

7678
private static String LOG_PREFIX = ":ReactNative:"
7779

78-
ReactNativeModules(Logger logger) {
80+
ReactNativeModules(Logger logger, String jsAppDir) {
7981
this.logger = logger
82+
this.jsAppDir = jsAppDir
8083

8184
def (nativeModules, packageName) = this.getReactNativeConfig()
8285
this.reactNativeModules = nativeModules
@@ -142,45 +145,66 @@ class ReactNativeModules {
142145
}
143146

144147
/**
145-
* Runs a process to call the React Native CLI Config command and parses the output
146-
*
147-
* @return ArrayList < HashMap < String , String > >
148+
* Runs a specified command using Runtime exec() in a specified directory.
149+
* Throws when the command result is empty.
148150
*/
149-
ArrayList<HashMap<String, String>> getReactNativeConfig() {
150-
if (this.reactNativeModules != null) return this.reactNativeModules
151-
ArrayList<HashMap<String, String>> reactNativeModules = new ArrayList<HashMap<String, String>>()
152-
153-
def cmdProcess
154-
def npx = Os.isFamily(Os.FAMILY_WINDOWS) ? "npx.cmd" : "npx"
155-
def command = "${npx} --quiet react-native config"
156-
def reactNativeConfigOutput = ""
157-
151+
String getCommandOutput(String command, File directory = null) {
158152
try {
159-
cmdProcess = Runtime.getRuntime().exec(command)
153+
def output = ""
154+
def cmdProcess = Runtime.getRuntime().exec(command, null, directory)
160155
def bufferedReader = new BufferedReader(new InputStreamReader(cmdProcess.getInputStream()))
161156
def buff = ""
162157
def readBuffer = new StringBuffer()
163-
while ((buff = bufferedReader.readLine()) != null){
164-
readBuffer.append(buff)
158+
while ((buff = bufferedReader.readLine()) != null) {
159+
readBuffer.append(buff)
160+
}
161+
output = readBuffer.toString()
162+
if (!output) {
163+
this.logger.error("${LOG_PREFIX}Unexpected empty result of running '${command}' command from '${directory}' directory.")
164+
def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream()))
165+
def errBuff = ""
166+
def readErrorBuffer = new StringBuffer()
167+
while ((errBuff = bufferedErrorReader.readLine()) != null) {
168+
readErrorBuffer.append(errBuff)
169+
}
170+
throw new Exception(readErrorBuffer.toString())
165171
}
166-
reactNativeConfigOutput = readBuffer.toString()
172+
return output
167173
} catch (Exception exception) {
168-
this.logger.warn("${LOG_PREFIX}${exception.message}")
169-
this.logger.warn("${LOG_PREFIX}Automatic import of native modules failed.")
174+
this.logger.error("${LOG_PREFIX}Running '${command}' command from '${directory}' directory failed.")
175+
throw exception
176+
}
177+
}
170178

171-
def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream()))
172-
def buff = ""
173-
def readBuffer = new StringBuffer()
174-
while ((buff = bufferedErrorReader.readLine()) != null){
175-
readBuffer.append(buff)
176-
}
177-
this.logger.warn("${LOG_PREFIX}${readBuffer.toString()}")
179+
/**
180+
* Runs a process to call the React Native CLI Config command and parses the output
181+
*/
182+
ArrayList<HashMap<String, String>> getReactNativeConfig() {
183+
if (this.reactNativeModules != null) return this.reactNativeModules
178184

179-
return reactNativeModules
185+
ArrayList<HashMap<String, String>> reactNativeModules = new ArrayList<HashMap<String, String>>()
186+
def npx = Os.isFamily(Os.FAMILY_WINDOWS) ? "npx.cmd" : "npx"
187+
def command = "${npx} --quiet --no-install react-native config"
188+
/**
189+
* Running npx from the directory of the JS app which holds this script in its node_modules.
190+
* We do so, because Gradle may be ran with a different directory as CWD, that's outside of JS project
191+
* (e.g. when running with -p flag), in which case npx wouldn't resolve correct `react-native` binary.
192+
*/
193+
def dir = new File(this.jsAppDir)
194+
def reactNativeConfigOutput = this.getCommandOutput(command, dir)
195+
def json
196+
try {
197+
json = new JsonSlurper().parseText(reactNativeConfigOutput)
198+
} catch (Exception exception) {
199+
this.logger.error("${LOG_PREFIX}Failed to parse React Native CLI configuration: ${exception.toString()}")
200+
throw new Exception("Failed to parse React Native CLI configuration. Expected running '${command}' command from '${dir}' directory to output valid JSON, but it didn't. This may be caused by npx resolving to a legacy global react-native binary. Please make sure to uninstall any global 'react-native' binaries: 'npm uninstall -g react-native react-native-cli' and try again")
180201
}
181-
182-
def json = new JsonSlurper().parseText(reactNativeConfigOutput)
183202
def dependencies = json["dependencies"]
203+
def project = json["project"]["android"]
204+
205+
if (project == null) {
206+
throw new Exception("React Native CLI failed to determine Android project configuration. This is likely due to misconfiguration. Config output:\n${json.toMapString()}")
207+
}
184208

185209
dependencies.each { name, value ->
186210
def platformsConfig = value["platforms"];
@@ -211,7 +235,7 @@ class ReactNativeModules {
211235
* Exported Extensions
212236
* ------------------------ */
213237

214-
def autoModules = new ReactNativeModules(logger)
238+
def autoModules = new ReactNativeModules(logger, jsAppDir)
215239

216240
ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings, String root = null ->
217241
if (root != null) {

0 commit comments

Comments
 (0)