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 { writeMetroConfig } from './writeMetroConfig'; 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 pkg, 61 platforms, 62 }); 63 64 profile(writeMetroConfig)(projectRoot, { pkg, templateDirectory }); 65 66 const depsResults = await profile(updatePackageJSONAsync)(projectRoot, { 67 templateDirectory, 68 pkg, 69 skipDependencyUpdate, 70 }); 71 72 return { 73 hasNewProjectFiles: !!copiedPaths.length, 74 // If the iOS folder changes or new packages are added, we should rerun pod install. 75 needsPodInstall: 76 copiedPaths.includes('ios') || 77 depsResults.hasNewDependencies || 78 depsResults.hasNewDevDependencies, 79 ...depsResults, 80 }; 81} 82 83/** 84 * Extract the template and copy the ios and android directories over to the project directory. 85 * 86 * @return `true` if any project files were created. 87 */ 88async function cloneTemplateAndCopyToProjectAsync({ 89 projectRoot, 90 templateDirectory, 91 template, 92 exp, 93 pkg, 94 platforms, 95}: { 96 projectRoot: string; 97 templateDirectory: string; 98 template?: string; 99 exp: Pick<ExpoConfig, 'name' | 'sdkVersion'>; 100 pkg: PackageJSONConfig; 101 platforms: ModPlatform[]; 102}): Promise<string[]> { 103 const ora = logNewSection( 104 'Creating native project directories (./ios and ./android) and updating .gitignore' 105 ); 106 107 try { 108 await cloneTemplateAsync({ templateDirectory, template, exp, ora }); 109 110 const results = await copyTemplateFilesAsync(projectRoot, { 111 pkg, 112 templateDirectory, 113 platforms, 114 }); 115 116 ora.succeed(createCopyFilesSuccessMessage(platforms, results)); 117 118 return results.copiedPaths; 119 } catch (e: any) { 120 if (!(e instanceof AbortCommandError)) { 121 Log.error(e.message); 122 } 123 ora.fail('Failed to create the native project.'); 124 Log.log( 125 chalk.yellow( 126 'You may want to delete the `./ios` and/or `./android` directories before trying again.' 127 ) 128 ); 129 throw new SilentError(e); 130 } 131} 132