18d307f52SEvan Baconimport { ExpoConfig, PackageJSONConfig } from '@expo/config';
28d307f52SEvan Baconimport { ModPlatform } from '@expo/config-plugins';
38d307f52SEvan Baconimport chalk from 'chalk';
48d307f52SEvan Bacon
58d307f52SEvan Baconimport { copyTemplateFilesAsync, createCopyFilesSuccessMessage } from './copyTemplateFiles';
68d307f52SEvan Baconimport { cloneTemplateAsync } from './resolveTemplate';
78d307f52SEvan Baconimport { DependenciesModificationResults, updatePackageJSONAsync } from './updatePackageJson';
85492d26aSCedric van Puttenimport { validateTemplatePlatforms } from './validateTemplatePlatforms';
98a424bebSJames Ideimport * as Log from '../log';
108a424bebSJames Ideimport { AbortCommandError, SilentError } from '../utils/errors';
118a424bebSJames Ideimport { logNewSection } from '../utils/ora';
128a424bebSJames Ideimport { profile } from '../utils/profile';
138d307f52SEvan Bacon
148d307f52SEvan Bacon/**
158d307f52SEvan Bacon * Creates local native files from an input template file path.
168d307f52SEvan Bacon *
178d307f52SEvan Bacon * @return `true` if the project is ejecting, and `false` if it's syncing.
188d307f52SEvan Bacon */
198d307f52SEvan Baconexport async function updateFromTemplateAsync(
208d307f52SEvan Bacon  projectRoot: string,
218d307f52SEvan Bacon  {
228d307f52SEvan Bacon    exp,
238d307f52SEvan Bacon    pkg,
248d307f52SEvan Bacon    template,
258d307f52SEvan Bacon    templateDirectory,
268d307f52SEvan Bacon    platforms,
278d307f52SEvan Bacon    skipDependencyUpdate,
288d307f52SEvan Bacon  }: {
298d307f52SEvan Bacon    /** Expo Config */
308d307f52SEvan Bacon    exp: ExpoConfig;
318d307f52SEvan Bacon    /** package.json as JSON */
328d307f52SEvan Bacon    pkg: PackageJSONConfig;
338d307f52SEvan Bacon    /** Template reference ID. */
348d307f52SEvan Bacon    template?: string;
358d307f52SEvan Bacon    /** Directory to write the template to before copying into the project. */
368d307f52SEvan Bacon    templateDirectory?: string;
378d307f52SEvan Bacon    /** List of platforms to clone. */
388d307f52SEvan Bacon    platforms: ModPlatform[];
398d307f52SEvan Bacon    /** List of dependencies to skip updating. */
408d307f52SEvan Bacon    skipDependencyUpdate?: string[];
418d307f52SEvan Bacon  }
428d307f52SEvan Bacon): Promise<
438d307f52SEvan Bacon  {
448d307f52SEvan Bacon    /** Indicates if new files were created in the project. */
458d307f52SEvan Bacon    hasNewProjectFiles: boolean;
468d307f52SEvan Bacon    /** Indicates that the project needs to run `pod install` */
478d307f52SEvan Bacon    needsPodInstall: boolean;
488d307f52SEvan Bacon  } & DependenciesModificationResults
498d307f52SEvan Bacon> {
508d307f52SEvan Bacon  if (!templateDirectory) {
518d307f52SEvan Bacon    const temporary = await import('tempy');
528d307f52SEvan Bacon    templateDirectory = temporary.directory();
538d307f52SEvan Bacon  }
548d307f52SEvan Bacon
558d307f52SEvan Bacon  const copiedPaths = await profile(cloneTemplateAndCopyToProjectAsync)({
568d307f52SEvan Bacon    projectRoot,
578d307f52SEvan Bacon    template,
588d307f52SEvan Bacon    templateDirectory,
598d307f52SEvan Bacon    exp,
608d307f52SEvan Bacon    platforms,
618d307f52SEvan Bacon  });
628d307f52SEvan Bacon
638d307f52SEvan Bacon  const depsResults = await profile(updatePackageJSONAsync)(projectRoot, {
648d307f52SEvan Bacon    templateDirectory,
658d307f52SEvan Bacon    pkg,
668d307f52SEvan Bacon    skipDependencyUpdate,
678d307f52SEvan Bacon  });
688d307f52SEvan Bacon
698d307f52SEvan Bacon  return {
708d307f52SEvan Bacon    hasNewProjectFiles: !!copiedPaths.length,
718d307f52SEvan Bacon    // If the iOS folder changes or new packages are added, we should rerun pod install.
728d307f52SEvan Bacon    needsPodInstall:
738d307f52SEvan Bacon      copiedPaths.includes('ios') ||
748d307f52SEvan Bacon      depsResults.hasNewDependencies ||
758d307f52SEvan Bacon      depsResults.hasNewDevDependencies,
768d307f52SEvan Bacon    ...depsResults,
778d307f52SEvan Bacon  };
788d307f52SEvan Bacon}
798d307f52SEvan Bacon
808d307f52SEvan Bacon/**
818d307f52SEvan Bacon * Extract the template and copy the ios and android directories over to the project directory.
828d307f52SEvan Bacon *
838d307f52SEvan Bacon * @return `true` if any project files were created.
848d307f52SEvan Bacon */
858d307f52SEvan Baconasync function cloneTemplateAndCopyToProjectAsync({
868d307f52SEvan Bacon  projectRoot,
878d307f52SEvan Bacon  templateDirectory,
888d307f52SEvan Bacon  template,
898d307f52SEvan Bacon  exp,
905492d26aSCedric van Putten  platforms: unknownPlatforms,
918d307f52SEvan Bacon}: {
928d307f52SEvan Bacon  projectRoot: string;
938d307f52SEvan Bacon  templateDirectory: string;
948d307f52SEvan Bacon  template?: string;
958d307f52SEvan Bacon  exp: Pick<ExpoConfig, 'name' | 'sdkVersion'>;
968d307f52SEvan Bacon  platforms: ModPlatform[];
978d307f52SEvan Bacon}): Promise<string[]> {
98*9f789f00SAlan Hughes  const platformDirectories = unknownPlatforms
99*9f789f00SAlan Hughes    .map((platform) => `./${platform}`)
100*9f789f00SAlan Hughes    .reverse()
101*9f789f00SAlan Hughes    .join(' and ');
102*9f789f00SAlan Hughes
103*9f789f00SAlan Hughes  const pluralized = unknownPlatforms.length > 1 ? 'directories' : 'directory';
1048d307f52SEvan Bacon  const ora = logNewSection(
105*9f789f00SAlan Hughes    `Creating native project ${pluralized} (${platformDirectories}) and updating .gitignore`
1068d307f52SEvan Bacon  );
1078d307f52SEvan Bacon
1088d307f52SEvan Bacon  try {
1098d307f52SEvan Bacon    await cloneTemplateAsync({ templateDirectory, template, exp, ora });
1108d307f52SEvan Bacon
1115492d26aSCedric van Putten    const platforms = await validateTemplatePlatforms({
1125492d26aSCedric van Putten      templateDirectory,
1135492d26aSCedric van Putten      platforms: unknownPlatforms,
1145492d26aSCedric van Putten    });
1155492d26aSCedric van Putten
1168d307f52SEvan Bacon    const results = await copyTemplateFilesAsync(projectRoot, {
1178d307f52SEvan Bacon      templateDirectory,
1188d307f52SEvan Bacon      platforms,
1198d307f52SEvan Bacon    });
1208d307f52SEvan Bacon
1218d307f52SEvan Bacon    ora.succeed(createCopyFilesSuccessMessage(platforms, results));
1228d307f52SEvan Bacon
1238d307f52SEvan Bacon    return results.copiedPaths;
1248d307f52SEvan Bacon  } catch (e: any) {
1258d307f52SEvan Bacon    if (!(e instanceof AbortCommandError)) {
1268d307f52SEvan Bacon      Log.error(e.message);
1278d307f52SEvan Bacon    }
1288d307f52SEvan Bacon    ora.fail('Failed to create the native project.');
1298d307f52SEvan Bacon    Log.log(
1308d307f52SEvan Bacon      chalk.yellow(
1318d307f52SEvan Bacon        'You may want to delete the `./ios` and/or `./android` directories before trying again.'
1328d307f52SEvan Bacon      )
1338d307f52SEvan Bacon    );
1348d307f52SEvan Bacon    throw new SilentError(e);
1358d307f52SEvan Bacon  }
1368d307f52SEvan Bacon}
137