1import chalk from 'chalk';
2import fs from 'fs-extra';
3import path from 'path';
4
5import { IOS_DIR } from '../../Constants';
6import logger from '../../Logger';
7import { copyFileWithTransformsAsync } from '../../Transforms';
8import { FileTransforms } from '../../Transforms.types';
9import { searchFilesAsync } from '../../Utils';
10import vendoredModulesTransforms from './transforms/vendoredModulesTransforms';
11
12const IOS_VENDORED_DIR = path.join(IOS_DIR, 'vendored');
13
14/**
15 * Versions iOS vendored modules.
16 */
17export async function versionVendoredModulesAsync(
18  sdkNumber: number,
19  filterModules: string[] | null
20): Promise<void> {
21  const prefix = `ABI${sdkNumber}_0_0`;
22  const config = vendoredModulesTransforms(prefix);
23  const baseTransforms = baseTransformsFactory(prefix);
24  const unversionedDir = path.join(IOS_VENDORED_DIR, 'unversioned');
25  const versionedDir = path.join(IOS_VENDORED_DIR, `sdk${sdkNumber}`);
26  const sourceDirents = (await fs.readdir(unversionedDir, { withFileTypes: true })).filter(
27    (dirent) => {
28      return dirent.isDirectory() && (!filterModules || filterModules.includes(dirent.name));
29    }
30  );
31
32  for (const { name } of sourceDirents) {
33    logger.info('�� Versioning vendored module %s', chalk.green(name));
34
35    const moduleConfig = config[name];
36    const sourceDirectory = path.join(unversionedDir, name);
37    const targetDirectory = path.join(versionedDir, name);
38    const files = await searchFilesAsync(sourceDirectory, '**');
39
40    await fs.remove(targetDirectory);
41
42    for (const sourceFile of files) {
43      await copyFileWithTransformsAsync({
44        sourceFile,
45        sourceDirectory,
46        targetDirectory,
47        transforms: {
48          path: [...baseTransforms.path, ...(moduleConfig?.path ?? [])],
49          content: [...baseTransforms.content, ...(moduleConfig?.content ?? [])],
50        },
51      });
52    }
53  }
54}
55
56/**
57 * Generates base transforms to apply for all vendored modules.
58 */
59function baseTransformsFactory(prefix: string): Required<FileTransforms> {
60  return {
61    path: [
62      {
63        find: /([^/]+\.podspec\.json)$\b/,
64        replaceWith: `${prefix}$1`,
65      },
66      {
67        find: /\b(RCT|RNC|RNG|RNR|REA|RNS)([^/]*\.)(h|m|mm)/,
68        replaceWith: `${prefix}$1$2$3`,
69      },
70    ],
71    content: [
72      {
73        paths: '*.podspec.json',
74        find: /"name": "([\w-]+)"/,
75        replaceWith: `"name": "${prefix}$1"`,
76      },
77      {
78        find: /\b(React)/g,
79        replaceWith: `${prefix}$1`,
80      },
81      {
82        find: /\b(RCT|RNC|RNG|RNR|REA|RNS)(\w+)\b/g,
83        replaceWith: `${prefix}$1$2`,
84      },
85      {
86        find: /facebook::/g,
87        replaceWith: `${prefix}facebook::`,
88      },
89      {
90        find: /react::/g,
91        replaceWith: `${prefix}React::`,
92      },
93      {
94        find: /using namespace (facebook|react)/g,
95        replaceWith: (_, p1) => {
96          return `using namespace ${prefix}${p1 === 'react' ? 'React' : p1}`;
97        },
98      },
99      {
100        // Objective-C only, see the comment in the rule below.
101        paths: '*.{h,m,mm}',
102        find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi,
103        replaceWith: `${prefix}R$1`,
104      },
105      {
106        // Swift translates uppercased letters at the beginning of the method name to lowercased letters, we must comply with it.
107        paths: '*.swift',
108        find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi,
109        replaceWith: (_, p1) => `${prefix.toLowerCase()}R${p1}`,
110      },
111      {
112        find: /<jsi\/(.*)\.h>/,
113        replaceWith: `<${prefix}jsi/${prefix}$1.h>`,
114      },
115      {
116        find: /(JSCExecutorFactory|HermesExecutorFactory)\.h/g,
117        replaceWith: `${prefix}$1.h`,
118      },
119      {
120        find: /viewForReactTag/g,
121        replaceWith: `viewFor${prefix}ReactTag`,
122      },
123      {
124        find: /isReactRootView/g,
125        replaceWith: `is${prefix}ReactRootView`,
126      },
127      {
128        // Prefix only unindented `@objc` (notice `^` and `m` flag in the pattern). Method names shouldn't get prefixed.
129        paths: '*.swift',
130        find: /^@objc\(([^)]+)\)/gm,
131        replaceWith: `@objc(${prefix}$1)`,
132      },
133      {
134        paths: '*.podspec.json',
135        find: new RegExp(`${prefix}React-${prefix}RCT`, 'g'),
136        replaceWith: `${prefix}React-RCT`,
137      },
138      {
139        paths: '*.podspec.json',
140        find: new RegExp(`${prefix}React-Core\\/${prefix}RCT`, 'g'),
141        replaceWith: `${prefix}React-Core/RCT`,
142      },
143    ],
144  };
145}
146