1import chalk from 'chalk'; 2import { PromisyClass, TaskQueue } from 'cwait'; 3import fs from 'fs-extra'; 4import os from 'os'; 5import path from 'path'; 6 7import logger from '../../Logger'; 8import { Package } from '../../Packages'; 9import { copyFileWithTransformsAsync } from '../../Transforms'; 10import { searchFilesAsync } from '../../Utils'; 11import { expoModulesTransforms } from './transforms/expoModulesTransforms'; 12import { getVersionPrefix, getVersionedDirectory } from './utils'; 13 14// Label of the console's timer used during versioning 15const TIMER_LABEL = 'Versioning expo modules finished in'; 16 17// The pattern that matches the dependency pods that need to be renamed in `*.podspec.json`. 18const PODSPEC_DEPS_TO_RENAME_PATTERN = /^(Expo|EX(?!GL_CPP)|UM|EAS|React|RCT|Yoga)/; 19 20/** 21 * Function that versions expo modules. 22 */ 23export async function versionExpoModulesAsync( 24 sdkNumber: number, 25 packages: Package[] 26): Promise<void> { 27 const prefix = getVersionPrefix(sdkNumber); 28 const transforms = expoModulesTransforms(prefix); 29 const versionedDirectory = getVersionedDirectory(sdkNumber); 30 const taskQueue = new TaskQueue(Promise as PromisyClass, os.cpus().length); 31 32 // Prepare versioning task (for single package). 33 const versionPackageTask = taskQueue.wrap(async (pkg) => { 34 logger.log(`- ${chalk.green(pkg.podspecName!)}`); 35 const sourceDirectory = path.join(pkg.path, pkg.iosSubdirectory); 36 const targetDirectory = path.join(versionedDirectory, pkg.podspecName!); 37 38 // Find all iOS files within the package, except the podspec. 39 // Podspecs depend on the corresponding `package.json`, 40 // that we don't want to copy (no need to version JS files, workspace project names duplication). 41 // Instead, we generate the static podspec in JSON format (see `generateVersionedPodspecAsync`). 42 const files = await searchFilesAsync(sourceDirectory, '**', { 43 ignore: [`${pkg.podspecName}.podspec`], 44 }); 45 46 // Ensure the target directory is empty 47 if (await fs.pathExists(targetDirectory)) { 48 await fs.remove(targetDirectory); 49 } 50 51 // Copy files to the new directory with applied transforms 52 for (const sourceFile of files) { 53 await copyFileWithTransformsAsync({ 54 sourceFile, 55 targetDirectory, 56 sourceDirectory, 57 transforms, 58 }); 59 } 60 61 // Create a podspec in JSON format so we don't have to keep `package.json`s 62 await generateVersionedPodspecAsync(pkg, prefix, targetDirectory); 63 }); 64 65 logger.info(' Versioning expo modules'); 66 console.time(TIMER_LABEL); 67 68 // Enqueue packages to version. 69 await Promise.all(packages.map(versionPackageTask)); 70 71 console.timeEnd(TIMER_LABEL); 72} 73 74/** 75 * Generates versioned static podspec in JSON format. 76 */ 77async function generateVersionedPodspecAsync( 78 pkg: Package, 79 prefix: string, 80 targetDirectory: string 81) { 82 const podspec = await pkg.getPodspecAsync(); 83 84 if (!podspec) { 85 throw new Error(`Podspec for package ${pkg.packageName} not found`); 86 } 87 if (podspec.name) { 88 podspec.name = `${prefix}${podspec.name}`; 89 } 90 if (podspec.header_dir) { 91 podspec.header_dir = `${prefix}${podspec.header_dir}`; 92 } 93 if (podspec.dependencies) { 94 Object.keys(podspec.dependencies) 95 .filter((key) => PODSPEC_DEPS_TO_RENAME_PATTERN.test(key)) 96 .forEach((key) => { 97 const newKey = `${prefix}${key}`; 98 podspec.dependencies[newKey] = podspec.dependencies[key]; 99 delete podspec.dependencies[key]; 100 }); 101 } 102 103 if (['expo-updates', 'expo-constants'].includes(pkg.packageName)) { 104 // For expo-updates and expo-constants in Expo Go, we don't need app.config and app.manifest in versioned code. 105 delete podspec['script_phases']; 106 delete podspec['resource_bundles']; 107 } 108 109 const targetPath = path.join(targetDirectory, `${prefix}${pkg.podspecName}.podspec.json`); 110 111 // Write a new one 112 await fs.outputJson(targetPath, podspec, { 113 spaces: 2, 114 }); 115} 116