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