18d307f52SEvan Baconimport { ModPlatform } from '@expo/config-plugins';
28d307f52SEvan Baconimport { MergeResults } from '@expo/config-plugins/build/utils/generateCode';
38d307f52SEvan Baconimport chalk from 'chalk';
48d307f52SEvan Baconimport fs from 'fs';
58d307f52SEvan Baconimport path from 'path';
68d307f52SEvan Bacon
78d307f52SEvan Baconimport { copySync, directoryExistsAsync } from '../utils/dir';
88d307f52SEvan Baconimport { mergeGitIgnorePaths } from '../utils/mergeGitIgnorePaths';
98d307f52SEvan Bacon
10474a7a4bSEvan Baconconst debug = require('debug')('expo:prebuild:copyTemplateFiles') as typeof console.log;
11474a7a4bSEvan Bacon
128d307f52SEvan Bacontype CopyFilesResults = {
138d307f52SEvan Bacon  /** Merge results for the root `.gitignore` file */
1429975bfdSEvan Bacon  gitignore: MergeResults | null;
158d307f52SEvan Bacon  /** List of file paths that were copied from the template into the project. */
168d307f52SEvan Bacon  copiedPaths: string[];
178d307f52SEvan Bacon  /** List of file paths that were skipped due to a number of factors. */
188d307f52SEvan Bacon  skippedPaths: string[];
198d307f52SEvan Bacon};
208d307f52SEvan Bacon
218d307f52SEvan Bacon/**
228d307f52SEvan Bacon * Return true if the given platforms all have an internal `.gitignore` file.
238d307f52SEvan Bacon *
248d307f52SEvan Bacon * @param projectRoot
258d307f52SEvan Bacon * @param platforms
268d307f52SEvan Bacon */
278d307f52SEvan Baconfunction hasAllPlatformSpecificGitIgnores(projectRoot: string, platforms: ModPlatform[]): boolean {
288d307f52SEvan Bacon  return platforms.reduce<boolean>(
298d307f52SEvan Bacon    (p, platform) => p && fs.existsSync(path.join(projectRoot, platform, '.gitignore')),
308d307f52SEvan Bacon    true
318d307f52SEvan Bacon  );
328d307f52SEvan Bacon}
338d307f52SEvan Bacon
348d307f52SEvan Bacon/** Create a custom log message based on the copy file results. */
358d307f52SEvan Baconexport function createCopyFilesSuccessMessage(
368d307f52SEvan Bacon  platforms: ModPlatform[],
378d307f52SEvan Bacon  { skippedPaths, gitignore }: CopyFilesResults
388d307f52SEvan Bacon): string {
398d307f52SEvan Bacon  let message = `Created native project${platforms.length > 1 ? 's' : ''}`;
408d307f52SEvan Bacon
418d307f52SEvan Bacon  if (skippedPaths.length) {
428d307f52SEvan Bacon    message += chalk.dim(
438d307f52SEvan Bacon      ` | ${skippedPaths.map((path) => chalk.bold(`/${path}`)).join(', ')} already created`
448d307f52SEvan Bacon    );
458d307f52SEvan Bacon  }
468d307f52SEvan Bacon  if (!gitignore) {
478d307f52SEvan Bacon    message += chalk.dim(` | gitignore skipped`);
488d307f52SEvan Bacon  } else if (!gitignore.didMerge) {
498d307f52SEvan Bacon    message += chalk.dim(` | gitignore already synced`);
508d307f52SEvan Bacon  } else if (gitignore.didMerge && gitignore.didClear) {
518d307f52SEvan Bacon    message += chalk.dim(` | synced gitignore`);
528d307f52SEvan Bacon  }
538d307f52SEvan Bacon  return message;
548d307f52SEvan Bacon}
558d307f52SEvan Bacon
568d307f52SEvan Bacon/** Copy template files into the project and possibly merge the `.gitignore` files.  */
578d307f52SEvan Baconexport async function copyTemplateFilesAsync(
588d307f52SEvan Bacon  projectRoot: string,
598d307f52SEvan Bacon  {
608d307f52SEvan Bacon    templateDirectory,
618d307f52SEvan Bacon    platforms,
628d307f52SEvan Bacon  }: {
638d307f52SEvan Bacon    /** File path to the template directory. */
648d307f52SEvan Bacon    templateDirectory: string;
658d307f52SEvan Bacon    /** List of platforms to copy against. */
668d307f52SEvan Bacon    platforms: ModPlatform[];
678d307f52SEvan Bacon  }
688d307f52SEvan Bacon): Promise<CopyFilesResults> {
698d307f52SEvan Bacon  const copyResults = await copyPathsFromTemplateAsync(projectRoot, {
708d307f52SEvan Bacon    templateDirectory,
71*4149568cSEvan Bacon    copyFilePaths: platforms,
728d307f52SEvan Bacon  });
738d307f52SEvan Bacon
748d307f52SEvan Bacon  const hasPlatformSpecificGitIgnores = hasAllPlatformSpecificGitIgnores(
758d307f52SEvan Bacon    templateDirectory,
768d307f52SEvan Bacon    platforms
778d307f52SEvan Bacon  );
78474a7a4bSEvan Bacon  debug(`All platforms have an internal gitignore: ${hasPlatformSpecificGitIgnores}`);
79*4149568cSEvan Bacon
80*4149568cSEvan Bacon  // TODO: Remove gitignore modifications -- maybe move to `npx expo-doctor`
818d307f52SEvan Bacon  const gitignore = hasPlatformSpecificGitIgnores
828d307f52SEvan Bacon    ? null
838d307f52SEvan Bacon    : mergeGitIgnorePaths(
848d307f52SEvan Bacon        path.join(projectRoot, '.gitignore'),
858d307f52SEvan Bacon        path.join(templateDirectory, '.gitignore')
868d307f52SEvan Bacon      );
878d307f52SEvan Bacon
888d307f52SEvan Bacon  return { ...copyResults, gitignore };
898d307f52SEvan Bacon}
908d307f52SEvan Bacon
918d307f52SEvan Baconasync function copyPathsFromTemplateAsync(
928d307f52SEvan Bacon  /** File path to the project. */
938d307f52SEvan Bacon  projectRoot: string,
948d307f52SEvan Bacon  {
958d307f52SEvan Bacon    templateDirectory,
968d307f52SEvan Bacon    copyFilePaths,
978d307f52SEvan Bacon  }: {
988d307f52SEvan Bacon    /** File path to the template project. */
998d307f52SEvan Bacon    templateDirectory: string;
1008d307f52SEvan Bacon    /** List of relative paths to copy from the template to the project. */
1018d307f52SEvan Bacon    copyFilePaths: string[];
1028d307f52SEvan Bacon  }
1038d307f52SEvan Bacon): Promise<Pick<CopyFilesResults, 'copiedPaths' | 'skippedPaths'>> {
1048d307f52SEvan Bacon  const copiedPaths = [];
1058d307f52SEvan Bacon  const skippedPaths = [];
1068d307f52SEvan Bacon  for (const copyFilePath of copyFilePaths) {
1078d307f52SEvan Bacon    const projectPath = path.join(projectRoot, copyFilePath);
1088d307f52SEvan Bacon    if (!(await directoryExistsAsync(projectPath))) {
1098d307f52SEvan Bacon      copiedPaths.push(copyFilePath);
1108d307f52SEvan Bacon      copySync(path.join(templateDirectory, copyFilePath), projectPath);
1118d307f52SEvan Bacon    } else {
1128d307f52SEvan Bacon      skippedPaths.push(copyFilePath);
1138d307f52SEvan Bacon    }
1148d307f52SEvan Bacon  }
115474a7a4bSEvan Bacon  debug(`Copied files:`, copiedPaths);
116474a7a4bSEvan Bacon  debug(`Skipped files:`, copiedPaths);
1178d307f52SEvan Bacon  return { copiedPaths, skippedPaths };
1188d307f52SEvan Bacon}
119