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