Repository: microsoft/react-native-test-app
Pull request #839 (2022-03-30 12:45:47)

fix: cannot find `react-native-test-app` when Metro starts
### Description

`react-native.config.js` is imported by `@react-native-community/cli` and may output warnings about not being able to find `react-native-test-app` if the library it's looking at uses our utility functions.

### Platforms affected

- [ ] Android
- [ ] iOS
- [ ] macOS
- [ ] Windows

### Test plan

CI should pass.


Patch:
diff --git a/example/react-native.config.js b/example/react-native.config.js
index 262d4b8..9b40783 100644
--- a/example/react-native.config.js
+++ b/example/react-native.config.js
@@ -1,24 +1,31 @@
-const fs = require("fs");
-const path = require("path");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require("react-native-test-app");
+const project = (() => {
+  const fs = require("fs");
+  const path = require("path");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require("react-native-test-app");
+    return {
+      android: {
+        sourceDir: "android",
+        manifestPath: androidManifestPath(path.join(__dirname, "android")),
+      },
+      ios: {
+        project: iosProjectPath("ios"),
+      },
+      windows: fs.existsSync("windows/Example.sln") && {
+        sourceDir: "windows",
+        solutionFile: "Example.sln",
+        project: windowsProjectPath(path.join(__dirname, "windows")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: "android",
-      manifestPath: androidManifestPath(path.join(__dirname, "android")),
-    },
-    ios: {
-      project: iosProjectPath("ios"),
-    },
-    windows: fs.existsSync("windows/Example.sln") && {
-      sourceDir: "windows",
-      solutionFile: "Example.sln",
-      project: windowsProjectPath(path.join(__dirname, "windows")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
diff --git a/scripts/configure.js b/scripts/configure.js
index bc55c16..ce73ea6 100755
--- a/scripts/configure.js
+++ b/scripts/configure.js
@@ -282,14 +282,22 @@ function reactNativeConfig({ name, testAppPath, platforms, flatten }) {
     switch (platforms[0]) {
       case "android":
         return join(
-          'const { androidManifestPath } = require("react-native-test-app");',
+          "const project = (() => {",
+          "  try {",
+          '    const { androidManifestPath } = require("react-native-test-app");',
+          "    return {",
+          "      android: {",
+          '        sourceDir: ".",',
+          "        manifestPath: androidManifestPath(__dirname),",
+          "      },",
+          "    };",
+          "  } catch (_) {",
+          "    return undefined;",
+          "  }",
+          "})();",
+          "",
           "module.exports = {",
-          "  project: {",
-          "    android: {",
-          '      sourceDir: ".",',
-          "      manifestPath: androidManifestPath(__dirname),",
-          "    },",
-          "  },",
+          "  ...(project ? { project } : undefined),",
           "};",
           ""
         );
@@ -297,31 +305,46 @@ function reactNativeConfig({ name, testAppPath, platforms, flatten }) {
       case "macos":
       case "ios":
         return join(
-          'const { iosProjectPath } = require("react-native-test-app");',
-          "module.exports = {",
-          "  project: {",
-          "    ios: {",
-          '      project: iosProjectPath("."),',
-          "    }",
+          "const project = (() => {",
+          "  try {",
+          '    const { iosProjectPath } = require("react-native-test-app");',
+          "    return {",
+          "      ios: {",
+          '        project: iosProjectPath("."),',
+          "      },",
+          "    };",
+          "  } catch (_) {",
+          "    return undefined;",
           "  }",
+          "})();",
+          "",
+          "module.exports = {",
+          "  ...(project ? { project } : undefined),",
           "};",
           ""
         );
 
       case "windows":
         return join(
-          'const path = require("path");',
-          'const { windowsProjectPath } = require("react-native-test-app");',
+          "const project = (() => {",
+          '  const path = require("path");',
+          '  const sourceDir = "windows";',
+          "  try {",
+          '    const { windowsProjectPath } = require("react-native-test-app");',
+          "    return {",
+          "      windows: {",
+          "        sourceDir,",
+          `        solutionFile: "${name}.sln",`,
+          "        project: windowsProjectPath(path.join(__dirname, sourceDir)),",
+          "      },",
+          "    };",
+          "  } catch (_) {",
+          "    return undefined;",
+          "  }",
+          "})();",
           "",
-          'const sourceDir = "windows";',
           "module.exports = {",
-          "  project: {",
-          "    windows: {",
-          "      sourceDir,",
-          `      solutionFile: "${name}.sln",`,
-          "      project: windowsProjectPath(path.join(__dirname, sourceDir)),",
-          "    },",
-          "  },",
+          "  ...(project ? { project } : undefined),",
           '  reactNativePath: "node_modules/react-native-windows",',
           "};",
           ""


Test patch:
diff --git a/test/configure/__snapshots__/gatherConfig.test.js.snap b/test/configure/__snapshots__/gatherConfig.test.js.snap
index 0eeeece..86e7058 100644
--- a/test/configure/__snapshots__/gatherConfig.test.js.snap
+++ b/test/configure/__snapshots__/gatherConfig.test.js.snap
@@ -24,13 +24,21 @@ use_test_app!
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const { iosProjectPath } = require(\\"react-native-test-app\\");
-module.exports = {
-  project: {
-    ios: {
-      project: iosProjectPath(\\".\\"),
-    }
+    "react-native.config.js": "const project = (() => {
+  try {
+    const { iosProjectPath } = require(\\"react-native-test-app\\");
+    return {
+      ios: {
+        project: iosProjectPath(\\".\\"),
+      },
+    };
+  } catch (_) {
+    return undefined;
   }
+})();
+
+module.exports = {
+  ...(project ? { project } : undefined),
 };
 ",
   },
@@ -126,29 +134,36 @@ use_test_app!
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
   },
@@ -193,57 +208,71 @@ Object {
     "common/metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "common/react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "common/react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
   },
@@ -278,29 +307,36 @@ use_test_app!
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
   },
@@ -405,29 +441,36 @@ use_test_app!
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
     "windows/.gitignore": Object {
@@ -539,29 +582,36 @@ use_test_app!
     "metro.config.js": Object {
       "source": "example/metro.config.js",
     },
-    "react-native.config.js": "const fs = require(\\"fs\\");
-const path = require(\\"path\\");
-const {
-  androidManifestPath,
-  iosProjectPath,
-  windowsProjectPath,
-} = require(\\"react-native-test-app\\");
+    "react-native.config.js": "const project = (() => {
+  const fs = require(\\"fs\\");
+  const path = require(\\"path\\");
+  try {
+    const {
+      androidManifestPath,
+      iosProjectPath,
+      windowsProjectPath,
+    } = require(\\"react-native-test-app\\");
+    return {
+      android: {
+        sourceDir: \\"android\\",
+        manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
+      },
+      ios: {
+        project: iosProjectPath(\\"ios\\"),
+      },
+      windows: fs.existsSync(\\"windows/Test.sln\\") && {
+        sourceDir: \\"windows\\",
+        solutionFile: \\"Test.sln\\",
+        project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
+      },
+    };
+  } catch (_) {
+    return undefined;
+  }
+})();
 
 module.exports = {
-  project: {
-    android: {
-      sourceDir: \\"android\\",
-      manifestPath: androidManifestPath(path.join(__dirname, \\"android\\")),
-    },
-    ios: {
-      project: iosProjectPath(\\"ios\\"),
-    },
-    windows: fs.existsSync(\\"windows/Test.sln\\") && {
-      sourceDir: \\"windows\\",
-      solutionFile: \\"Test.sln\\",
-      project: windowsProjectPath(path.join(__dirname, \\"windows\\")),
-    },
-  },
+  ...(project ? { project } : undefined),
 };
 ",
   },