1import { ExpoConfig } from '@expo/config'; 2import { ModPlatform } from '@expo/config-plugins'; 3 4import * as Log from '../log'; 5import { installNodeDependenciesAsync, resolvePackageManager } from '../utils/nodeModules'; 6import { logNewSection } from '../utils/ora'; 7import { profile } from '../utils/profile'; 8import { clearNativeFolder, promptToClearMalformedNativeProjectsAsync } from './clearNativeFolder'; 9import { configureProjectAsync } from './configureProjectAsync'; 10import { ensureConfigAsync } from './ensureConfigAsync'; 11import { assertPlatforms, ensureValidPlatforms, resolveTemplateOption } from './resolveOptions'; 12import { updateFromTemplateAsync } from './updateFromTemplate'; 13 14export type PrebuildResults = { 15 /** Expo config. */ 16 exp: ExpoConfig; 17 /** Indicates if the process created new files. */ 18 hasNewProjectFiles: boolean; 19 /** The platforms that were prebuilt. */ 20 platforms: ModPlatform[]; 21 /** Indicates if pod install was run. */ 22 podInstall: boolean; 23 /** Indicates if node modules were installed. */ 24 nodeInstall: boolean; 25 /** Indicates which package manager the project is using. */ 26 packageManager: string; 27}; 28 29/** 30 * Entry point into the prebuild process, delegates to other helpers to perform various steps. 31 * 32 * 0. Attempt to clean the project folders. 33 * 1. Create native projects (ios, android). 34 * 2. Install node modules. 35 * 3. Apply config to native projects. 36 * 4. Install CocoaPods. 37 */ 38export async function prebuildAsync( 39 projectRoot: string, 40 options: { 41 /** Should install node modules and cocoapods. */ 42 install: boolean; 43 /** List of platforms to prebuild. */ 44 platforms: ModPlatform[]; 45 /** Should delete the native folders before attempting to prebuild. */ 46 clean?: boolean; 47 /** URL or file path to the prebuild template. */ 48 template?: string; 49 /** Name of the node package manager to install with. */ 50 packageManager?: 'npm' | 'yarn'; 51 /** List of node modules to skip updating. */ 52 skipDependencyUpdate?: string[]; 53 } 54): Promise<PrebuildResults | null> { 55 if (options.clean) { 56 const { maybeBailOnGitStatusAsync } = await import('../utils/git'); 57 // Clean the project folders... 58 if (await maybeBailOnGitStatusAsync()) { 59 return null; 60 } 61 // Clear the native folders before syncing 62 await clearNativeFolder(projectRoot, options.platforms); 63 } else { 64 // Check if the existing project folders are malformed. 65 await promptToClearMalformedNativeProjectsAsync(projectRoot, options.platforms); 66 } 67 68 // Warn if the project is attempting to prebuild an unsupported platform (iOS on Windows). 69 options.platforms = ensureValidPlatforms(options.platforms); 70 // Assert if no platforms are left over after filtering. 71 assertPlatforms(options.platforms); 72 73 // Get the Expo config, create it if missing. 74 const { exp, pkg } = await ensureConfigAsync(projectRoot, { platforms: options.platforms }); 75 76 // Create native projects from template. 77 const { hasNewProjectFiles, needsPodInstall, hasNewDependencies } = await updateFromTemplateAsync( 78 projectRoot, 79 { 80 exp, 81 pkg, 82 template: options.template != null ? resolveTemplateOption(options.template) : undefined, 83 platforms: options.platforms, 84 skipDependencyUpdate: options.skipDependencyUpdate, 85 } 86 ); 87 88 // Install node modules 89 const packageManager = resolvePackageManager({ 90 install: options.install, 91 npm: options.packageManager === 'npm', 92 yarn: options.packageManager === 'yarn', 93 }); 94 95 if (options.install) { 96 await installNodeDependenciesAsync(projectRoot, packageManager, { 97 // We delete the dependencies when new ones are added because native packages are more fragile. 98 // npm doesn't work well so we always run the cleaning step when npm is used in favor of yarn. 99 clean: hasNewDependencies || packageManager === 'npm', 100 }); 101 } 102 103 // Apply Expo config to native projects 104 const configSyncingStep = logNewSection('Config syncing'); 105 try { 106 await profile(configureProjectAsync)(projectRoot, { 107 platforms: options.platforms, 108 }); 109 configSyncingStep.succeed('Config synced'); 110 } catch (error) { 111 configSyncingStep.fail('Config sync failed'); 112 throw error; 113 } 114 115 // Install CocoaPods 116 let podsInstalled: boolean = false; 117 // err towards running pod install less because it's slow and users can easily run npx pod-install afterwards. 118 if (options.platforms.includes('ios') && options.install && needsPodInstall) { 119 const { installCocoaPodsAsync } = await import('../utils/cocoapods'); 120 121 podsInstalled = await installCocoaPodsAsync(projectRoot); 122 } else { 123 Log.debug('Skipped pod install'); 124 } 125 126 return { 127 packageManager, 128 nodeInstall: options.install, 129 podInstall: !podsInstalled, 130 platforms: options.platforms, 131 hasNewProjectFiles, 132 exp, 133 }; 134} 135