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