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