1import { ExpoConfig } from '@expo/config'; 2import { ModPlatform } from '@expo/config-plugins'; 3 4import { installAsync } from '../install/installAsync'; 5import { env } from '../utils/env'; 6import { setNodeEnv } from '../utils/nodeEnv'; 7import { clearNodeModulesAsync } from '../utils/nodeModules'; 8import { logNewSection } from '../utils/ora'; 9import { profile } from '../utils/profile'; 10import { clearNativeFolder, promptToClearMalformedNativeProjectsAsync } from './clearNativeFolder'; 11import { configureProjectAsync } from './configureProjectAsync'; 12import { ensureConfigAsync } from './ensureConfigAsync'; 13import { assertPlatforms, ensureValidPlatforms, resolveTemplateOption } from './resolveOptions'; 14import { updateFromTemplateAsync } from './updateFromTemplate'; 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 }; 57 /** List of node modules to skip updating. */ 58 skipDependencyUpdate?: string[]; 59 } 60): Promise<PrebuildResults | null> { 61 setNodeEnv('development'); 62 require('@expo/env').load(projectRoot); 63 64 if (options.clean) { 65 const { maybeBailOnGitStatusAsync } = await import('../utils/git'); 66 // Clean the project folders... 67 if (await maybeBailOnGitStatusAsync()) { 68 return null; 69 } 70 // Clear the native folders before syncing 71 await clearNativeFolder(projectRoot, options.platforms); 72 } else { 73 // Check if the existing project folders are malformed. 74 await promptToClearMalformedNativeProjectsAsync(projectRoot, options.platforms); 75 } 76 77 // Warn if the project is attempting to prebuild an unsupported platform (iOS on Windows). 78 options.platforms = ensureValidPlatforms(options.platforms); 79 // Assert if no platforms are left over after filtering. 80 assertPlatforms(options.platforms); 81 82 // Get the Expo config, create it if missing. 83 const { exp, pkg } = await ensureConfigAsync(projectRoot, { platforms: options.platforms }); 84 85 // Create native projects from template. 86 const { hasNewProjectFiles, needsPodInstall, hasNewDependencies } = await updateFromTemplateAsync( 87 projectRoot, 88 { 89 exp, 90 pkg, 91 template: options.template != null ? resolveTemplateOption(options.template) : undefined, 92 platforms: options.platforms, 93 skipDependencyUpdate: options.skipDependencyUpdate, 94 } 95 ); 96 97 // Install node modules 98 if (options.install) { 99 if (hasNewDependencies && options.packageManager?.npm) { 100 await clearNodeModulesAsync(projectRoot); 101 } 102 103 await installAsync([], { 104 npm: !!options.packageManager?.npm, 105 yarn: !!options.packageManager?.yarn, 106 pnpm: !!options.packageManager?.pnpm, 107 silent: !env.EXPO_DEBUG, 108 }); 109 } 110 111 // Apply Expo config to native projects 112 const configSyncingStep = logNewSection('Config syncing'); 113 try { 114 await profile(configureProjectAsync)(projectRoot, { 115 platforms: options.platforms, 116 }); 117 configSyncingStep.succeed('Config synced'); 118 } catch (error) { 119 configSyncingStep.fail('Config sync failed'); 120 throw error; 121 } 122 123 // Install CocoaPods 124 let podsInstalled: boolean = false; 125 // err towards running pod install less because it's slow and users can easily run npx pod-install afterwards. 126 if (options.platforms.includes('ios') && options.install && needsPodInstall) { 127 const { installCocoaPodsAsync } = await import('../utils/cocoapods'); 128 129 podsInstalled = await installCocoaPodsAsync(projectRoot); 130 } else { 131 debug('Skipped pod install'); 132 } 133 134 return { 135 nodeInstall: !!options.install, 136 podInstall: !podsInstalled, 137 platforms: options.platforms, 138 hasNewProjectFiles, 139 exp, 140 }; 141} 142