1import { ExpoConfig, PackageJSONConfig } from '@expo/config'; 2import { ModPlatform } from '@expo/config-plugins'; 3import chalk from 'chalk'; 4 5import * as Log from '../log'; 6import { AbortCommandError, SilentError } from '../utils/errors'; 7import { logNewSection } from '../utils/ora'; 8import { profile } from '../utils/profile'; 9import { copyTemplateFilesAsync, createCopyFilesSuccessMessage } from './copyTemplateFiles'; 10import { cloneTemplateAsync } from './resolveTemplate'; 11import { DependenciesModificationResults, updatePackageJSONAsync } from './updatePackageJson'; 12import { validateTemplatePlatforms } from './validateTemplatePlatforms'; 13import { writeMetroConfig } from './writeMetroConfig'; 14 15/** 16 * Creates local native files from an input template file path. 17 * 18 * @return `true` if the project is ejecting, and `false` if it's syncing. 19 */ 20export async function updateFromTemplateAsync( 21 projectRoot: string, 22 { 23 exp, 24 pkg, 25 template, 26 templateDirectory, 27 platforms, 28 skipDependencyUpdate, 29 }: { 30 /** Expo Config */ 31 exp: ExpoConfig; 32 /** package.json as JSON */ 33 pkg: PackageJSONConfig; 34 /** Template reference ID. */ 35 template?: string; 36 /** Directory to write the template to before copying into the project. */ 37 templateDirectory?: string; 38 /** List of platforms to clone. */ 39 platforms: ModPlatform[]; 40 /** List of dependencies to skip updating. */ 41 skipDependencyUpdate?: string[]; 42 } 43): Promise< 44 { 45 /** Indicates if new files were created in the project. */ 46 hasNewProjectFiles: boolean; 47 /** Indicates that the project needs to run `pod install` */ 48 needsPodInstall: boolean; 49 } & DependenciesModificationResults 50> { 51 if (!templateDirectory) { 52 const temporary = await import('tempy'); 53 templateDirectory = temporary.directory(); 54 } 55 56 const copiedPaths = await profile(cloneTemplateAndCopyToProjectAsync)({ 57 projectRoot, 58 template, 59 templateDirectory, 60 exp, 61 pkg, 62 platforms, 63 }); 64 65 profile(writeMetroConfig)(projectRoot, { pkg, templateDirectory }); 66 67 const depsResults = await profile(updatePackageJSONAsync)(projectRoot, { 68 templateDirectory, 69 pkg, 70 skipDependencyUpdate, 71 }); 72 73 return { 74 hasNewProjectFiles: !!copiedPaths.length, 75 // If the iOS folder changes or new packages are added, we should rerun pod install. 76 needsPodInstall: 77 copiedPaths.includes('ios') || 78 depsResults.hasNewDependencies || 79 depsResults.hasNewDevDependencies, 80 ...depsResults, 81 }; 82} 83 84/** 85 * Extract the template and copy the ios and android directories over to the project directory. 86 * 87 * @return `true` if any project files were created. 88 */ 89async function cloneTemplateAndCopyToProjectAsync({ 90 projectRoot, 91 templateDirectory, 92 template, 93 exp, 94 pkg, 95 platforms: unknownPlatforms, 96}: { 97 projectRoot: string; 98 templateDirectory: string; 99 template?: string; 100 exp: Pick<ExpoConfig, 'name' | 'sdkVersion'>; 101 pkg: PackageJSONConfig; 102 platforms: ModPlatform[]; 103}): Promise<string[]> { 104 const ora = logNewSection( 105 'Creating native project directories (./ios and ./android) and updating .gitignore' 106 ); 107 108 try { 109 await cloneTemplateAsync({ templateDirectory, template, exp, ora }); 110 111 const platforms = await validateTemplatePlatforms({ 112 templateDirectory, 113 platforms: unknownPlatforms, 114 }); 115 116 const results = await copyTemplateFilesAsync(projectRoot, { 117 pkg, 118 templateDirectory, 119 platforms, 120 }); 121 122 ora.succeed(createCopyFilesSuccessMessage(platforms, results)); 123 124 return results.copiedPaths; 125 } catch (e: any) { 126 if (!(e instanceof AbortCommandError)) { 127 Log.error(e.message); 128 } 129 ora.fail('Failed to create the native project.'); 130 Log.log( 131 chalk.yellow( 132 'You may want to delete the `./ios` and/or `./android` directories before trying again.' 133 ) 134 ); 135 throw new SilentError(e); 136 } 137} 138