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