From 78a4df8d26a06052224a632e18d9a5d6d0cedf68 Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Tue, 16 Jan 2024 17:42:25 -0500 Subject: [PATCH] feat(react-native): generate pod install target (#21166) --- e2e/expo/src/expo-pcv3.test.ts | 45 ++++++++++++++++++- .../src/react-native-pcv3.test.ts | 2 +- e2e/react-native/src/react-native.test.ts | 2 +- packages/expo/plugins/plugin.ts | 23 +++++----- .../generators/application/lib/add-project.ts | 8 ++-- packages/expo/src/generators/init/init.ts | 13 ++---- packages/expo/src/utils/has-expo-plugin.ts | 10 +++++ packages/react-native/plugins/plugin.ts | 17 +++++-- .../src/generators/application/application.ts | 8 ++++ .../react-native/src/generators/init/init.ts | 1 + 10 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 packages/expo/src/utils/has-expo-plugin.ts diff --git a/e2e/expo/src/expo-pcv3.test.ts b/e2e/expo/src/expo-pcv3.test.ts index 4235ee38594dc..d688f51e3bcb0 100644 --- a/e2e/expo/src/expo-pcv3.test.ts +++ b/e2e/expo/src/expo-pcv3.test.ts @@ -8,7 +8,10 @@ import { runCommandUntil, killProcessAndPorts, checkFilesExist, + updateFile, + runCLIAsync, } from 'e2e/utils'; +import { join } from 'path'; describe('@nx/expo/plugin', () => { let project: string; @@ -54,7 +57,7 @@ describe('@nx/expo/plugin', () => { try { process = await runCommandUntil( - `start ${appName} --port=${port}`, + `start ${appName} -- --port=${port}`, (output) => output.includes(`http://localhost:8081`) ); } catch (err) { @@ -66,4 +69,44 @@ describe('@nx/expo/plugin', () => { await killProcessAndPorts(process.pid, port); } }); + + it('should serve the app', async () => { + let process: ChildProcess; + const port = 8081; + + try { + process = await runCommandUntil( + `serve ${appName} -- --port=${port}`, + (output) => output.includes(`http://localhost:8081`) + ); + } catch (err) { + console.error(err); + } + + // port and process cleanup + if (process && process.pid) { + await killProcessAndPorts(process.pid, port); + } + }); + + it('should prebuild', async () => { + // run prebuild command with git check disable + // set a mock package name for ios and android in expo's app.json + const appJsonPath = join(appName, `app.json`); + const appJson = await readJson(appJsonPath); + if (appJson.expo.ios) { + appJson.expo.ios.bundleIdentifier = 'nx.test'; + } + if (appJson.expo.android) { + appJson.expo.android.package = 'nx.test'; + } + updateFile(appJsonPath, JSON.stringify(appJson)); + + // run prebuild command with git check disable + process.env['EXPO_NO_GIT_STATUS'] = 'true'; + const prebuildResult = await runCLIAsync( + `prebuild ${appName} --no-interactive --install=false` + ); + expect(prebuildResult.combinedOutput).toContain('Config synced'); + }); }); diff --git a/e2e/react-native/src/react-native-pcv3.test.ts b/e2e/react-native/src/react-native-pcv3.test.ts index 95d830c3366d8..bdc4993ac2a79 100644 --- a/e2e/react-native/src/react-native-pcv3.test.ts +++ b/e2e/react-native/src/react-native-pcv3.test.ts @@ -17,7 +17,7 @@ describe('@nx/react-native/plugin', () => { newProject(); appName = uniq('app'); runCLI( - `generate @nx/react-native:app ${appName} --project-name-and-root-format=as-provided --no-interactive`, + `generate @nx/react-native:app ${appName} --project-name-and-root-format=as-provided --install=false --no-interactive`, { env: { NX_PCV3: 'true' } } ); }); diff --git a/e2e/react-native/src/react-native.test.ts b/e2e/react-native/src/react-native.test.ts index dc47b1cb4cf63..d3d2eab7b17c4 100644 --- a/e2e/react-native/src/react-native.test.ts +++ b/e2e/react-native/src/react-native.test.ts @@ -229,7 +229,7 @@ describe('react native', () => { const libName = uniq('@my-org/lib1'); runCLI( - `generate @nx/react-native:application ${appName} --project-name-and-root-format=as-provided --no-interactive` + `generate @nx/react-native:application ${appName} --project-name-and-root-format=as-provided --install=false --no-interactive` ); // check files are generated without the layout directory ("apps/") and diff --git a/packages/expo/plugins/plugin.ts b/packages/expo/plugins/plugin.ts index fae07d28d1900..f19279e2de546 100644 --- a/packages/expo/plugins/plugin.ts +++ b/packages/expo/plugins/plugin.ts @@ -18,6 +18,7 @@ import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory'; export interface ExpoPluginOptions { startTargetName?: string; + serveTargetName?: string; runIosTargetName?: string; runAndroidTargetName?: string; exportTargetName?: string; @@ -67,7 +68,6 @@ export const createNodes: CreateNodes = [ const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); if ( !siblingFiles.includes('package.json') || - !siblingFiles.includes('project.json') || !siblingFiles.includes('metro.config.js') ) { return {}; @@ -107,16 +107,17 @@ function buildExpoTargets( const targets: Record = { [options.startTargetName]: { - command: `expo start`, + executor: `@nx/expo:start`, + }, + [options.serveTargetName]: { + command: `expo start --web`, options: { cwd: projectRoot }, }, [options.runIosTargetName]: { - command: `expo run:ios`, - options: { cwd: projectRoot }, + executor: `@nx/expo:run-ios`, }, [options.runAndroidTargetName]: { - command: `expo run:android`, - options: { cwd: projectRoot }, + executor: `@nx/expo:run-android`, }, [options.exportTargetName]: { command: `expo export`, @@ -139,18 +140,15 @@ function buildExpoTargets( options: { cwd: workspaceRoot }, // install at workspace root }, [options.prebuildTargetName]: { - command: `expo prebuild`, - options: { cwd: projectRoot }, + executor: `@nx/expo:prebuild`, }, [options.buildTargetName]: { - command: `eas build`, - options: { cwd: projectRoot }, + executor: `@nx/expo:build`, dependsOn: [`^${options.buildTargetName}`], inputs: getInputs(namedInputs), }, [options.submitTargetName]: { - command: `eas submit`, - options: { cwd: projectRoot }, + executor: `@nx/expo:submit`, dependsOn: [`^${options.submitTargetName}`], inputs: getInputs(namedInputs), }, @@ -208,6 +206,7 @@ function load(path: string): any { function normalizeOptions(options: ExpoPluginOptions): ExpoPluginOptions { options ??= {}; options.startTargetName ??= 'start'; + options.serveTargetName ??= 'serve'; options.runIosTargetName ??= 'run-ios'; options.runAndroidTargetName ??= 'run-android'; options.exportTargetName ??= 'export'; diff --git a/packages/expo/src/generators/application/lib/add-project.ts b/packages/expo/src/generators/application/lib/add-project.ts index 18051b1226961..0b7d9c18b205e 100644 --- a/packages/expo/src/generators/application/lib/add-project.ts +++ b/packages/expo/src/generators/application/lib/add-project.ts @@ -6,15 +6,13 @@ import { TargetConfiguration, Tree, } from '@nx/devkit'; + +import { hasExpoPlugin } from '../../../utils/has-expo-plugin'; import { NormalizedSchema } from './normalize-options'; export function addProject(host: Tree, options: NormalizedSchema) { const nxJson = readNxJson(host); - const hasPlugin = nxJson.plugins?.some((p) => - typeof p === 'string' - ? p === '@nx/expo/plugin' - : p.plugin === '@nx/expo/plugin' - ); + const hasPlugin = hasExpoPlugin(host); const projectConfiguration: ProjectConfiguration = { root: options.appProjectRoot, diff --git a/packages/expo/src/generators/init/init.ts b/packages/expo/src/generators/init/init.ts index 234f1712e1c9b..640f9793f5bf5 100644 --- a/packages/expo/src/generators/init/init.ts +++ b/packages/expo/src/generators/init/init.ts @@ -18,6 +18,7 @@ import { reactNativeVersion, reactVersion, } from '../../utils/versions'; +import { hasExpoPlugin } from '../../utils/has-expo-plugin'; import { addGitIgnoreEntry } from './lib/add-git-ignore-entry'; import { Schema } from './schema'; @@ -64,18 +65,12 @@ function moveDependency(host: Tree) { function addPlugin(host: Tree) { const nxJson = readNxJson(host); - nxJson.plugins ??= []; - for (const plugin of nxJson.plugins) { - if ( - typeof plugin === 'string' - ? plugin === '@nx/expo/plugin' - : plugin.plugin === '@nx/expo/plugin' - ) { - return; - } + if (hasExpoPlugin(host)) { + return; } + nxJson.plugins ??= []; nxJson.plugins.push({ plugin: '@nx/expo/plugin', options: { diff --git a/packages/expo/src/utils/has-expo-plugin.ts b/packages/expo/src/utils/has-expo-plugin.ts new file mode 100644 index 0000000000000..b7aba22e1e5cf --- /dev/null +++ b/packages/expo/src/utils/has-expo-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasExpoPlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/expo/plugin' + : p.plugin === '@nx/expo/plugin' + ); +} diff --git a/packages/react-native/plugins/plugin.ts b/packages/react-native/plugins/plugin.ts index f779740da1593..a041e6704423f 100644 --- a/packages/react-native/plugins/plugin.ts +++ b/packages/react-native/plugins/plugin.ts @@ -3,6 +3,7 @@ import { CreateNodes, CreateNodesContext, detectPackageManager, + joinPathFragments, NxJsonConfiguration, readJsonFile, TargetConfiguration, @@ -17,6 +18,7 @@ import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory'; export interface ReactNativePluginOptions { startTargetName?: string; + podInstallTargetName?: string; runIosTargetName?: string; runAndroidTargetName?: string; buildIosTargetName?: string; @@ -63,7 +65,6 @@ export const createNodes: CreateNodes = [ const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); if ( !siblingFiles.includes('package.json') || - !siblingFiles.includes('project.json') || !siblingFiles.includes('metro.config.js') ) { return {}; @@ -102,8 +103,17 @@ function buildReactNativeTargets( const targets: Record = { [options.startTargetName]: { - command: `react-native start`, - options: { cwd: projectRoot }, + executor: `@nx/react-native:start`, + }, + [options.podInstallTargetName]: { + command: `pod install`, + options: { cwd: joinPathFragments(projectRoot, 'ios') }, + cache: true, + inputs: getInputs(namedInputs), + outputs: [ + getOutputs(projectRoot, 'ios/Pods'), + getOutputs(projectRoot, 'ios/Podfile.lock'), + ], }, [options.runIosTargetName]: { command: `react-native run-ios`, @@ -191,6 +201,7 @@ function normalizeOptions( ): ReactNativePluginOptions { options ??= {}; options.startTargetName ??= 'start'; + options.podInstallTargetName ??= 'pod-install'; options.runIosTargetName ??= 'run-ios'; options.runAndroidTargetName ??= 'run-android'; options.buildIosTargetName ??= 'build-ios'; diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts index 181dda6526700..6019cd12e17d8 100644 --- a/packages/react-native/src/generators/application/application.ts +++ b/packages/react-native/src/generators/application/application.ts @@ -11,6 +11,7 @@ import { runSymlink } from '../../utils/symlink-task'; import { addLinting } from '../../utils/add-linting'; import { addJest } from '../../utils/add-jest'; import { chmodAndroidGradlewFilesTask } from '../../utils/chmod-android-gradle-files'; +import { runPodInstall } from '../../utils/pod-install-task'; import { normalizeOptions } from './lib/normalize-options'; import initGenerator from '../init/init'; @@ -79,6 +80,13 @@ export async function reactNativeApplicationGeneratorInternal( ); tasks.push(chmodTaskGradlewTask); + const podInstallTask = runPodInstall( + joinPathFragments(host.root, options.iosProjectRoot) + ); + if (options.install) { + tasks.push(podInstallTask); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/react-native/src/generators/init/init.ts b/packages/react-native/src/generators/init/init.ts index 1dc9d2c0954e6..829547f4eaa22 100644 --- a/packages/react-native/src/generators/init/init.ts +++ b/packages/react-native/src/generators/init/init.ts @@ -81,6 +81,7 @@ function addPlugin(host: Tree) { plugin: '@nx/react-native/plugin', options: { startTargetName: 'start', + podInstallTargetName: 'pod-install', bundleTargetName: 'bundle', runIosTargetName: 'run-ios', runAndroidTargetName: 'run-android',