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