diff --git a/packages/expo/generators.json b/packages/expo/generators.json index d0ddaf795de177..b28603b46635af 100644 --- a/packages/expo/generators.json +++ b/packages/expo/generators.json @@ -28,6 +28,11 @@ "schema": "./src/generators/component/schema.json", "description": "Create a component", "aliases": ["c"] + }, + "convert-to-inferred": { + "factory": "./src/generators/convert-to-inferred/convert-to-inferred", + "schema": "./src/generators/convert-to-inferred/schema.json", + "description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target." } } } diff --git a/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.spec.ts b/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.spec.ts new file mode 100644 index 00000000000000..ade66e9d09d113 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.spec.ts @@ -0,0 +1,482 @@ +import { + addProjectConfiguration, + type ExpandedPluginConfiguration, + joinPathFragments, + type ProjectConfiguration, + type ProjectGraph, + readNxJson, + readProjectConfiguration, + type Tree, + writeJson, +} from '@nx/devkit'; +import { TempFs } from '@nx/devkit/internal-testing-utils'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { join } from 'node:path'; +import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration'; +import { convertToInferred } from './convert-to-inferred'; + +let fs: TempFs; +let projectGraph: ProjectGraph; +jest.mock('@nx/devkit', () => ({ + ...jest.requireActual('@nx/devkit'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(() => Promise.resolve(projectGraph)), + updateProjectConfiguration: jest + .fn() + .mockImplementation((tree, projectName, projectConfiguration) => { + function handleEmptyTargets( + projectName: string, + projectConfiguration: ProjectConfiguration + ): void { + if ( + projectConfiguration.targets && + !Object.keys(projectConfiguration.targets).length + ) { + // Re-order `targets` to appear after the `// target` comment. + delete projectConfiguration.targets; + projectConfiguration[ + '// targets' + ] = `to see all targets run: nx show project ${projectName} --web`; + projectConfiguration.targets = {}; + } else { + delete projectConfiguration['// targets']; + } + } + + const projectConfigFile = joinPathFragments( + projectConfiguration.root, + 'project.json' + ); + + if (!tree.exists(projectConfigFile)) { + throw new Error( + `Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.` + ); + } + handleEmptyTargets(projectName, projectConfiguration); + writeJson(tree, projectConfigFile, { + name: projectConfiguration.name ?? projectName, + $schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration), + ...projectConfiguration, + root: undefined, + }); + projectGraph.nodes[projectName].data = projectConfiguration; + }), +})); +jest.mock('nx/src/devkit-internals', () => ({ + ...jest.requireActual('nx/src/devkit-internals'), + getExecutorInformation: jest + .fn() + .mockImplementation((pkg, ...args) => + jest + .requireActual('nx/src/devkit-internals') + .getExecutorInformation('@nx/webpack', ...args) + ), +})); + +function addProject(tree: Tree, name: string, project: ProjectConfiguration) { + addProjectConfiguration(tree, name, project); + projectGraph.nodes[name] = { + name: name, + type: project.projectType === 'application' ? 'app' : 'lib', + data: { + projectType: project.projectType, + root: project.root, + targets: project.targets, + }, + }; +} + +interface ProjectOptions { + appName: string; + appRoot: string; + buildTargetName: string; + buildExecutor: string; + exportTargetName: string; + exportExecutor: string; + installTargetName: string; + installExecutor: string; + prebuildTargetName: string; + prebuildExecutor: string; + runIosTargetName: string; + runIosExecutor: string; + runAndroidTargetName: string; + runAndroidExecutor: string; + serveTargetName: string; + serveExecutor: string; + startTargetName: string; + startExecutor: string; + submitTargetName: string; + submitExecutor: string; +} + +const defaultProjectOptions: ProjectOptions = { + appName: 'demo', + appRoot: 'apps/demo', + buildTargetName: 'build', + buildExecutor: '@nx/expo:build', + exportTargetName: 'export', + exportExecutor: '@nx/expo:export', + installTargetName: 'install', + installExecutor: '@nx/expo:install', + prebuildTargetName: 'prebuild', + prebuildExecutor: '@nx/expo:prebuild', + runAndroidTargetName: 'run-android', + runAndroidExecutor: '@nx/expo:run', + runIosTargetName: 'run-ios', + runIosExecutor: '@nx/expo:run', + serveTargetName: 'serve', + serveExecutor: '@nx/expo:serve', + startTargetName: 'start', + startExecutor: '@nx/expo:start', + submitTargetName: 'submit', + submitExecutor: '@nx/expo:submit', +}; + +const defaultExpoConfig = { + expo: { + name: 'demo', + slug: 'demo', + version: '1.0.0', + orientation: 'portrait', + icon: './assets/icon.png', + splash: { + image: './assets/splash.png', + resizeMode: 'contain', + backgroundColor: '#ffffff', + }, + updates: { + fallbackToCacheTimeout: 0, + }, + assetBundlePatterns: ['**/*'], + ios: { + supportsTablet: true, + bundleIdentifier: 'com.anonymous.demo', + }, + android: { + adaptiveIcon: { + foregroundImage: './assets/adaptive-icon.png', + backgroundColor: '#FFFFFF', + }, + }, + web: { + favicon: './assets/favicon.png', + bundler: 'metro', + }, + plugins: [], + }, +}; + +function writeExpoConfig( + tree: Tree, + projectRoot: string, + expoConfig = defaultExpoConfig +) { + tree.write(`${projectRoot}/app.json`, JSON.stringify(expoConfig)); + fs.createFileSync(`${projectRoot}/app.json`, JSON.stringify(expoConfig)); + jest.doMock(join(fs.tempDir, projectRoot, 'app.json'), () => expoConfig, { + virtual: true, + }); +} + +function createProject( + tree: Tree, + options: Partial = {}, + additionalTargetOptions?: Record> +) { + let projectOptions = { ...defaultProjectOptions, ...options }; + const project: ProjectConfiguration = { + name: projectOptions.appName, + root: projectOptions.appRoot, + projectType: 'application', + targets: { + [projectOptions.buildTargetName]: { + executor: projectOptions.buildExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.buildTargetName], + }, + }, + [projectOptions.exportTargetName]: { + executor: projectOptions.exportExecutor, + options: { + platform: 'all', + outputDir: `dist/${projectOptions.appName}`, + ...additionalTargetOptions?.[projectOptions.exportTargetName], + }, + }, + [projectOptions.installTargetName]: { + executor: projectOptions.installExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.installTargetName], + }, + }, + [projectOptions.prebuildTargetName]: { + executor: projectOptions.prebuildExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.prebuildTargetName], + }, + }, + [projectOptions.runAndroidTargetName]: { + executor: projectOptions.runAndroidExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.runAndroidTargetName], + }, + }, + [projectOptions.runIosTargetName]: { + executor: projectOptions.runIosExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.runIosTargetName], + }, + }, + [projectOptions.serveTargetName]: { + executor: projectOptions.serveExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.startTargetName], + }, + }, + [projectOptions.startTargetName]: { + executor: projectOptions.startExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.serveTargetName], + }, + }, + [projectOptions.submitTargetName]: { + executor: projectOptions.submitExecutor, + options: { + ...additionalTargetOptions?.[projectOptions.submitTargetName], + }, + }, + }, + }; + + addProject(tree, project.name, project); + fs.createFileSync( + `${projectOptions.appRoot}/project.json`, + JSON.stringify(project) + ); + + // These file need to exist for inference, but they can be empty for the convert generator + fs.createFileSync(`${projectOptions.appRoot}/package.json`, '{}'); + fs.createFileSync(`${projectOptions.appRoot}/metro.config.js`, '// empty'); + + return project; +} + +describe('convert-to-inferred', () => { + let tree: Tree; + + beforeEach(() => { + fs = new TempFs('expo'); + tree = createTreeWithEmptyWorkspace(); + tree.root = fs.tempDir; + + projectGraph = { + nodes: {}, + dependencies: {}, + externalNodes: {}, + }; + }); + + afterEach(() => { + fs.cleanup(); + jest.resetModules(); + }); + + it('should convert project to use inference plugin', async () => { + const project = createProject(tree); + writeExpoConfig(tree, project.root); + + const project2 = createProject(tree, { + appName: 'app2', + appRoot: 'apps/app2', + }); + + const project2Build = project2.targets.build; + + await convertToInferred(tree, { project: project.name }); + + const nxJsonPlugins = readNxJson(tree).plugins; + const expoPlugin = nxJsonPlugins.find( + (plugin): plugin is ExpandedPluginConfiguration => + typeof plugin !== 'string' && plugin.plugin === '@nx/expo/plugin' + ); + + const projectConfig = readProjectConfiguration(tree, project.name); + + expect(expoPlugin).toBeDefined(); + expect(projectConfig.targets).toEqual({ + export: { + options: { + args: ['--output-dir=../../dist/demo', '--platform=all'], + }, + }, + }); + + const updatedProject2 = readProjectConfiguration(tree, project2.name); + expect(updatedProject2.targets.build).toEqual(project2Build); + }); + + it('should migrate options to CLI options and args', async () => { + const project = createProject( + tree, + {}, + { + build: { + wait: true, + clearCache: true, + }, + export: { bytecode: false, minify: false, platform: 'android' }, + 'run-android': { + platform: 'android', + variant: 'release', + clean: true, + bundler: false, + }, + 'run-ios': { + platform: 'ios', + xcodeConfiguration: 'Release', + buildCache: false, + }, + install: { check: true }, + prebuild: { clean: true }, + serve: { dev: false }, + start: { dev: false }, + submit: { wait: true, interactive: false }, + } + ); + writeExpoConfig(tree, project.root); + + await convertToInferred(tree, { project: project.name }); + + const projectConfig = readProjectConfiguration(tree, project.name); + expect(projectConfig.targets.build.options).toEqual({ + args: ['--wait', '--clear-cache'], + }); + expect(projectConfig.targets.export.options).toEqual({ + args: [ + '--no-minify', + '--no-bytecode', + '--output-dir=../../dist/demo', + '--platform=android', + ], + }); + expect(projectConfig.targets.install.options).toEqual({ + args: ['--check'], + }); + expect(projectConfig.targets.prebuild.options).toEqual({ + args: ['--clean'], + }); + expect(projectConfig.targets['run-android'].options).toEqual({ + args: ['--variant', 'release', '--no-bundler'], + }); + expect(projectConfig.targets['run-ios'].options).toEqual({ + args: ['--configuration', 'Release', '--no-build-cache'], + }); + expect(projectConfig.targets.serve.options).toEqual({ + args: ['--no-dev'], + }); + expect(projectConfig.targets.start.options).toEqual({ + args: ['--no-dev'], + }); + expect(projectConfig.targets.submit.options).toEqual({ + args: ['--non-interactive', '--wait'], + }); + }); + + it('should migrate custom run:ios and run:android target names', async () => { + const project1 = createProject( + tree, + { + appName: 'app1', + appRoot: 'apps/app1', + runAndroidTargetName: 'run-android-custom-1', + runIosTargetName: 'run-ios-custom-1', + }, + { + 'run-android-custom-1': { + platform: 'android', + buildCache: false, + }, + 'run-ios-custom-1': { + platform: 'ios', + buildCache: false, + }, + } + ); + + const project2 = createProject( + tree, + { + appName: 'app2', + appRoot: 'apps/app2', + runAndroidTargetName: 'run-android-custom-2', + runIosTargetName: 'run-ios-custom-2', + }, + { + 'run-android-custom-2': { + platform: 'android', + variant: 'release', + }, + 'run-ios-custom-2': { + platform: 'ios', + xcodeConfiguration: 'Release', + }, + } + ); + + writeExpoConfig(tree, project2.root); + writeExpoConfig(tree, project1.root); + + await convertToInferred(tree, {}); + + const config1 = readProjectConfiguration(tree, project1.name); + const config2 = readProjectConfiguration(tree, project2.name); + const nxJson = readNxJson(tree); + + expect(config1.targets['run-android-custom-1'].options).toEqual({ + args: ['--no-build-cache'], + }); + expect(config1.targets['run-ios-custom-1'].options).toEqual({ + args: ['--no-build-cache'], + }); + expect(config2.targets['run-android-custom-2'].options).toEqual({ + args: ['--variant', 'release'], + }); + expect(config2.targets['run-ios-custom-2'].options).toEqual({ + args: ['--configuration', 'Release'], + }); + expect(nxJson.plugins).toEqual([ + { + plugin: '@nx/expo/plugin', + options: { + buildTargetName: 'build', + exportTargetName: 'export', + installTargetName: 'install', + prebuildTargetName: 'prebuild', + runAndroidTargetName: 'run-android-custom-1', + runIosTargetName: 'run-ios-custom-1', + serveTargetName: 'serve', + startTargetName: 'start', + submitTargetName: 'submit', + }, + include: ['apps/app1/**/*'], + }, + { + plugin: '@nx/expo/plugin', + options: { + buildTargetName: 'build', + exportTargetName: 'export', + installTargetName: 'install', + prebuildTargetName: 'prebuild', + runAndroidTargetName: 'run-android-custom-2', + runIosTargetName: 'run-ios-custom-2', + serveTargetName: 'serve', + startTargetName: 'start', + submitTargetName: 'submit', + }, + include: ['apps/app2/**/*'], + }, + ]); + }); +}); diff --git a/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.ts new file mode 100644 index 00000000000000..5f6875d44ef47a --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -0,0 +1,169 @@ +import { + createProjectGraphAsync, + formatFiles, + getProjects, + type Tree, +} from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; +import { createNodes } from '../../../plugins/plugin'; +import { processBuildOptions } from './lib/process-build-options'; +import { postTargetTransformer } from './lib/post-target-transformer'; +import { processExportOptions } from './lib/process-export-options'; +import { processRunOptions } from './lib/process-run-options'; +import { processServeOptions } from './lib/process-serve-options'; +import { processStartOptions } from './lib/process-start-options'; +import { processSubmitOptions } from './lib/process-submit-options'; +import { processPrebuildOptions } from './lib/process-prebuild-options'; +import { processInstallOptions } from './lib/process-install-options'; + +interface Schema { + project?: string; + skipFormat?: boolean; +} + +export async function convertToInferred(tree: Tree, options: Schema) { + const projectGraph = await createProjectGraphAsync(); + const migrationLogs = new AggregatedLog(); + const projects = getProjects(tree); + const migratedProjects = await migrateProjectExecutorsToPluginV1( + tree, + projectGraph, + '@nx/expo/plugin', + createNodes, + { + buildTargetName: 'build', + exportTargetName: 'export', + installTargetName: 'install', + prebuildTargetName: 'prebuild', + runAndroidTargetName: 'run-android', + runIosTargetName: 'run-ios', + serveTargetName: 'serve', + startTargetName: 'start', + submitTargetName: 'submit', + }, + [ + { + executors: ['@nx/expo:build'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processBuildOptions + ), + targetPluginOptionMapper: (targetName) => ({ + buildTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:export'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processExportOptions + ), + targetPluginOptionMapper: (targetName) => ({ + exportTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:install'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processInstallOptions + ), + targetPluginOptionMapper: (targetName) => ({ + installTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:prebuild'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processPrebuildOptions + ), + targetPluginOptionMapper: (targetName) => ({ + prebuildTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:run'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processRunOptions + ), + targetPluginOptionMapper: (targetName) => { + // Assumption: There are no targets with the same name but different platforms. + // Most users will likely keep the `run-ios` and `run-android` target names that are generated. + // Otherwise, we look for the first target with a matching name, and use that platform. + const platform = getPlatformForFirstMatchedTarget( + targetName, + '@nx/expo:run', + projects + ); + return { + [platform === 'android' + ? 'runAndroidTargetName' + : 'runIosTargetName']: targetName, + }; + }, + }, + { + executors: ['@nx/expo:serve'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processServeOptions + ), + targetPluginOptionMapper: (targetName) => ({ + serveTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:start'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processStartOptions + ), + targetPluginOptionMapper: (targetName) => ({ + startTargetName: targetName, + }), + }, + { + executors: ['@nx/expo:submit'], + postTargetTransformer: postTargetTransformer( + migrationLogs, + processSubmitOptions + ), + targetPluginOptionMapper: (targetName) => ({ + submitTargetName: targetName, + }), + }, + ], + options.project + ); + + if (migratedProjects.size === 0) { + throw new Error('Could not find any targets to migrate.'); + } + + if (!options.skipFormat) { + await formatFiles(tree); + } + + return () => { + migrationLogs.flushLogs(); + }; +} + +function getPlatformForFirstMatchedTarget( + targetName: string, + executorName: string, + projects: Map +): string { + for (const [, project] of projects) { + const target = project.targets[targetName]; + if (target && target.executor === executorName) { + return target.options.platform; + } + } + return 'ios'; +} + +export default convertToInferred; diff --git a/packages/expo/src/generators/convert-to-inferred/lib/post-target-transformer.ts b/packages/expo/src/generators/convert-to-inferred/lib/post-target-transformer.ts new file mode 100644 index 00000000000000..667c80c3d83140 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/post-target-transformer.ts @@ -0,0 +1,67 @@ +import { type TargetConfiguration, type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processTargetOutputs } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils'; + +export function postTargetTransformer( + migrationLogs: AggregatedLog, + processOptions: ( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog + ) => void +) { + return ( + target: TargetConfiguration, + tree: Tree, + projectDetails: { projectName: string; root: string }, + inferredTargetConfiguration: TargetConfiguration + ) => { + if (target.options) { + processOptions( + tree, + target.options, + projectDetails.projectName, + projectDetails.root, + migrationLogs + ); + } + + if (target.configurations) { + for (const configurationName in target.configurations) { + const configuration = target.configurations[configurationName]; + processOptions( + tree, + configuration, + projectDetails.projectName, + projectDetails.root, + migrationLogs + ); + } + + if (Object.keys(target.configurations).length === 0) { + if ('defaultConfiguration' in target) { + delete target.defaultConfiguration; + } + delete target.configurations; + } + + if ( + 'defaultConfiguration' in target && + !target.configurations[target.defaultConfiguration] + ) { + delete target.defaultConfiguration; + } + } + + if (target.outputs) { + processTargetOutputs(target, [], inferredTargetConfiguration, { + projectName: projectDetails.projectName, + projectRoot: projectDetails.root, + }); + } + + return target; + }; +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-build-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-build-options.ts new file mode 100644 index 00000000000000..3535dc6642c461 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-build-options.ts @@ -0,0 +1,24 @@ +import { Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processBuildOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +): void { + const args: string[] = []; + if ('interactive' in options && options.interactive === false) { + args.push('--non-interactive'); + delete options.interactive; + } + if ('wait' in options && options.wait === false) { + if (options.wait) args.push('--wait'); + else args.push('--no-wait'); + delete options.wait; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-export-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-export-options.ts new file mode 100644 index 00000000000000..adea36a96c741e --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-export-options.ts @@ -0,0 +1,31 @@ +import { joinPathFragments, offsetFromRoot, type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processExportOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +) { + const args: string[] = []; + if ('minify' in options) { + if (options.minify === false) args.push('--no-minify'); + delete options.minify; + } + if ('bytecode' in options) { + if (options.bytecode === false) args.push('--no-bytecode'); + delete options.bytecode; + } + if ('outputDir' in options) { + const value = joinPathFragments( + offsetFromRoot(projectRoot), + options.outputDir + ); + args.push(`--output-dir=${value}`); + delete options.outputDir; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-generic-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-generic-options.ts new file mode 100644 index 00000000000000..96ee749bf6626f --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-generic-options.ts @@ -0,0 +1,23 @@ +import { names, type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; + +export function processGenericOptions( + _tree: Tree, + options: any, + _projectName: string, + _projectRoot: string, + _migrationLogs: AggregatedLog +) { + const args = options.args ?? []; + for (const key of Object.keys(options)) { + if (key === 'args') continue; + let value = options[key]; + if (typeof value === 'boolean') { + if (value) args.push(`--${names(key).fileName}`); + } else { + args.push(`--${names(key).fileName}=${value}`); + } + delete options[key]; + } + options.args = args; +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-install-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-install-options.ts new file mode 100644 index 00000000000000..88c61dd12834e4 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-install-options.ts @@ -0,0 +1,23 @@ +import { Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processInstallOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +): void { + const args: string[] = []; + // Technically this will not be set in project.json since the value is passed through CLI e.g. nx run :install foo,bar. + // Handling it here for correctness. + if ('packages' in options) { + const v = options.packages; + const packages = typeof v === 'string' ? v.split(',') : v; + args.push(...packages); + delete options.packages; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-prebuild-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-prebuild-options.ts new file mode 100644 index 00000000000000..7315eced8630f5 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-prebuild-options.ts @@ -0,0 +1,19 @@ +import { Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processPrebuildOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +): void { + const args: string[] = []; + if ('install' in options && options.install === false) { + args.push('--no-install'); + delete options.install; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-run-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-run-options.ts new file mode 100644 index 00000000000000..616fef72b92212 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-run-options.ts @@ -0,0 +1,39 @@ +import { names, type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; + +export function processRunOptions( + _tree: Tree, + options: any, + projectName: string, + _projectRoot: string, + migrationLogs: AggregatedLog +) { + const args: string[] = []; + + for (const key of Object.keys(options)) { + const v = options[key]; + if (key === 'xcodeConfiguration') { + args.push('--configuration', v); + } else if (typeof v === 'boolean') { + // no need to pass in the flag when it is true, pass the --no- when it is false. e.g. --no-build-cache, --no-bundler + if (v === false) { + args.push(`--no-${names(key).fileName}`); + } + } else { + if (key === 'platform') { + // Platform isn't necessary to pass to the CLI since it is already part of the inferred command. e.g. run:ios, run:android + } else if (key === 'clean') { + migrationLogs.addLog({ + project: projectName, + executorName: '@nx/export:run', + log: 'Unable to migrate "clean" option. Use `nx run :prebuild --clean` to regenerate native files.', + }); + } else { + args.push(`--${names(key).fileName}`, v); + } + } + delete options[key]; + } + + options.args = args; +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-serve-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-serve-options.ts new file mode 100644 index 00000000000000..3523a5e6f03f28 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-serve-options.ts @@ -0,0 +1,19 @@ +import { type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processServeOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +) { + const args: string[] = []; + if ('dev' in options) { + if (options.dev === false) args.push('--no-dev'); + delete options.dev; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-start-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-start-options.ts new file mode 100644 index 00000000000000..7dc6ac62c46712 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-start-options.ts @@ -0,0 +1,19 @@ +import { type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processStartOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +) { + const args: string[] = []; + if ('dev' in options) { + if (options.dev === false) args.push('--no-dev'); + delete options.dev; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +} diff --git a/packages/expo/src/generators/convert-to-inferred/lib/process-submit-options.ts b/packages/expo/src/generators/convert-to-inferred/lib/process-submit-options.ts new file mode 100644 index 00000000000000..b866bf439b8386 --- /dev/null +++ b/packages/expo/src/generators/convert-to-inferred/lib/process-submit-options.ts @@ -0,0 +1,24 @@ +import { type Tree } from '@nx/devkit'; +import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; +import { processGenericOptions } from './process-generic-options'; + +export function processSubmitOptions( + tree: Tree, + options: any, + projectName: string, + projectRoot: string, + migrationLogs: AggregatedLog +) { + const args: string[] = []; + if ('interactive' in options && options.interactive === false) { + args.push('--non-interactive'); + delete options.interactive; + } + if ('wait' in options) { + if (options.wait) args.push('--wait'); + else args.push('--no-wait'); + delete options.wait; + } + options.args = args; + processGenericOptions(tree, options, projectName, projectRoot, migrationLogs); +}