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        // Prefixes `React` with word-boundary and also in `insertReactSubview`, `removeReactSubview`.
79        find: /(\b|insert|remove)(React)/g,
80        replaceWith: `$1${prefix}$2`,
81      },
82      {
83        find: /\b(RCT|RNC|RNG|RNR|REA|RNS)(\w+)\b/g,
84        replaceWith: `${prefix}$1$2`,
85      },
86      {
87        find: /facebook::/g,
88        replaceWith: `${prefix}facebook::`,
89      },
90      {
91        find: /react::/g,
92        replaceWith: `${prefix}React::`,
93      },
94      {
95        find: /using namespace (facebook|react)/g,
96        replaceWith: (_, p1) => {
97          return `using namespace ${prefix}${p1 === 'react' ? 'React' : p1}`;
98        },
99      },
100      {
101        // Objective-C only, see the comment in the rule below.
102        paths: '*.{h,m,mm}',
103        find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi,
104        replaceWith: `${prefix}R$1`,
105      },
106      {
107        // Swift translates uppercased letters at the beginning of the method name to lowercased letters, we must comply with it.
108        paths: '*.swift',
109        find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi,
110        replaceWith: (_, p1) => `${prefix.toLowerCase()}R${p1}`,
111      },
112      {
113        find: /<jsi\/(.*)\.h>/,
114        replaceWith: `<${prefix}jsi/${prefix}$1.h>`,
115      },
116      {
117        find: /(JSCExecutorFactory|HermesExecutorFactory)\.h/g,
118        replaceWith: `${prefix}$1.h`,
119      },
120      {
121        find: /viewForReactTag/g,
122        replaceWith: `viewFor${prefix}ReactTag`,
123      },
124      {
125        find: /isReactRootView/g,
126        replaceWith: `is${prefix}ReactRootView`,
127      },
128      {
129        // Prefix only unindented `@objc` (notice `^` and `m` flag in the pattern). Method names shouldn't get prefixed.
130        paths: '*.swift',
131        find: /^@objc\(([^)]+)\)/gm,
132        replaceWith: `@objc(${prefix}$1)`,
133      },
134      {
135        paths: '*.podspec.json',
136        find: new RegExp(`${prefix}React-${prefix}RCT`, 'g'),
137        replaceWith: `${prefix}React-RCT`,
138      },
139      {
140        paths: '*.podspec.json',
141        find: new RegExp(`${prefix}React-Core\\/${prefix}RCT`, 'g'),
142        replaceWith: `${prefix}React-Core/RCT`,
143      },
144    ],
145  };
146}
147