1eeffdb10STomasz Sapetaimport * as fs from 'fs-extra';
2eeffdb10STomasz Sapetaimport walkSync from 'klaw-sync';
3eeffdb10STomasz Sapetaimport * as path from 'path';
4eeffdb10STomasz Sapeta
5eeffdb10STomasz Sapetaimport { ModuleConfiguration } from './ModuleConfiguration';
6eeffdb10STomasz Sapeta
7eeffdb10STomasz Sapetatype PreparedPrefixes = [nameWithExpoPrefix: string, nameWithoutExpoPrefix: string];
8eeffdb10STomasz Sapeta
9eeffdb10STomasz Sapeta/**
10eeffdb10STomasz Sapeta * prepares _Expo_ prefixes for specified name
11eeffdb10STomasz Sapeta * @param name module name, e.g. JS package name
12eeffdb10STomasz Sapeta * @param prefix prefix to prepare with, defaults to _Expo_
13eeffdb10STomasz Sapeta * @returns tuple `[nameWithPrefix: string, nameWithoutPrefix: string]`
14eeffdb10STomasz Sapeta */
15eeffdb10STomasz Sapetaconst preparePrefixes = (name: string, prefix: string = 'Expo'): PreparedPrefixes =>
16eeffdb10STomasz Sapeta  name.startsWith(prefix) ? [name, name.substr(prefix.length)] : [`${prefix}${name}`, name];
17eeffdb10STomasz Sapeta
18eeffdb10STomasz Sapetaconst asyncForEach = async <T>(
19eeffdb10STomasz Sapeta  array: T[],
20eeffdb10STomasz Sapeta  callback: (value: T, index: number, array: T[]) => Promise<void>
21eeffdb10STomasz Sapeta) => {
22eeffdb10STomasz Sapeta  for (let index = 0; index < array.length; index++) {
23eeffdb10STomasz Sapeta    await callback(array[index], index, array);
24eeffdb10STomasz Sapeta  }
25eeffdb10STomasz Sapeta};
26eeffdb10STomasz Sapeta
27eeffdb10STomasz Sapeta/**
28eeffdb10STomasz Sapeta * Removes specified files. If one file doesn't exist already, skips it
29eeffdb10STomasz Sapeta * @param directoryPath directory containing files to remove
30eeffdb10STomasz Sapeta * @param filenames array of filenames to remove
31eeffdb10STomasz Sapeta */
32eeffdb10STomasz Sapetaasync function removeFiles(directoryPath: string, filenames: string[]) {
33eeffdb10STomasz Sapeta  await Promise.all(filenames.map((filename) => fs.remove(path.resolve(directoryPath, filename))));
34eeffdb10STomasz Sapeta}
35eeffdb10STomasz Sapeta
36eeffdb10STomasz Sapeta/**
37eeffdb10STomasz Sapeta * Renames files names
38eeffdb10STomasz Sapeta * @param directoryPath - directory that holds files to be renamed
39eeffdb10STomasz Sapeta * @param extensions - array of extensions for files that would be renamed, must be provided with leading dot or empty for no extension, e.g. ['.html', '']
40eeffdb10STomasz Sapeta * @param renamings - array of filenames and their replacers
41eeffdb10STomasz Sapeta */
42eeffdb10STomasz Sapetaconst renameFilesWithExtensions = async (
43eeffdb10STomasz Sapeta  directoryPath: string,
44eeffdb10STomasz Sapeta  extensions: string[],
45eeffdb10STomasz Sapeta  renamings: { from: string; to: string }[]
46eeffdb10STomasz Sapeta) => {
47eeffdb10STomasz Sapeta  await asyncForEach(
48eeffdb10STomasz Sapeta    renamings,
49eeffdb10STomasz Sapeta    async ({ from, to }) =>
50eeffdb10STomasz Sapeta      await asyncForEach(extensions, async (extension) => {
51eeffdb10STomasz Sapeta        const fromFilename = `${from}${extension}`;
52eeffdb10STomasz Sapeta        if (!fs.existsSync(path.join(directoryPath, fromFilename))) {
53eeffdb10STomasz Sapeta          return;
54eeffdb10STomasz Sapeta        }
55eeffdb10STomasz Sapeta        const toFilename = `${to}${extension}`;
56eeffdb10STomasz Sapeta        await fs.rename(
57eeffdb10STomasz Sapeta          path.join(directoryPath, fromFilename),
58eeffdb10STomasz Sapeta          path.join(directoryPath, toFilename)
59eeffdb10STomasz Sapeta        );
60eeffdb10STomasz Sapeta      })
61eeffdb10STomasz Sapeta  );
62eeffdb10STomasz Sapeta};
63eeffdb10STomasz Sapeta
64eeffdb10STomasz Sapeta/**
65eeffdb10STomasz Sapeta * Enters each file recursively in provided dir and replaces content by invoking provided callback function
66eeffdb10STomasz Sapeta * @param directoryPath - root directory
67eeffdb10STomasz Sapeta * @param replaceFunction - function that converts current content into something different
68eeffdb10STomasz Sapeta */
69eeffdb10STomasz Sapetaconst replaceContents = async (
70eeffdb10STomasz Sapeta  directoryPath: string,
71eeffdb10STomasz Sapeta  replaceFunction: (contentOfSingleFile: string) => string
72eeffdb10STomasz Sapeta) => {
73eeffdb10STomasz Sapeta  await Promise.all(
74eeffdb10STomasz Sapeta    walkSync(directoryPath, { nodir: true }).map((file) =>
75eeffdb10STomasz Sapeta      replaceContent(file.path, replaceFunction)
76eeffdb10STomasz Sapeta    )
77eeffdb10STomasz Sapeta  );
78eeffdb10STomasz Sapeta};
79eeffdb10STomasz Sapeta
80eeffdb10STomasz Sapeta/**
81eeffdb10STomasz Sapeta * Replaces content in file. Does nothing if the file doesn't exist
82eeffdb10STomasz Sapeta * @param filePath - provided file
83eeffdb10STomasz Sapeta * @param replaceFunction - function that converts current content into something different
84eeffdb10STomasz Sapeta */
85eeffdb10STomasz Sapetaconst replaceContent = async (
86eeffdb10STomasz Sapeta  filePath: string,
87eeffdb10STomasz Sapeta  replaceFunction: (contentOfSingleFile: string) => string
88eeffdb10STomasz Sapeta) => {
89eeffdb10STomasz Sapeta  if (!fs.existsSync(filePath)) {
90eeffdb10STomasz Sapeta    return;
91eeffdb10STomasz Sapeta  }
92eeffdb10STomasz Sapeta
93eeffdb10STomasz Sapeta  const content = await fs.readFile(filePath, 'utf8');
94eeffdb10STomasz Sapeta  const newContent = replaceFunction(content);
95eeffdb10STomasz Sapeta  if (newContent !== content) {
96eeffdb10STomasz Sapeta    await fs.writeFile(filePath, newContent);
97eeffdb10STomasz Sapeta  }
98eeffdb10STomasz Sapeta};
99eeffdb10STomasz Sapeta
100eeffdb10STomasz Sapeta/**
101eeffdb10STomasz Sapeta * Removes all empty subdirs up to and including dirPath
102eeffdb10STomasz Sapeta * Recursively enters all subdirs and removes them if one is empty or cantained only empty subdirs
103eeffdb10STomasz Sapeta * @param dirPath - directory path that is being inspected
104eeffdb10STomasz Sapeta * @returns whether the given base directory and any empty subdirectories were deleted or not
105eeffdb10STomasz Sapeta */
106eeffdb10STomasz Sapetaconst removeUponEmptyOrOnlyEmptySubdirs = async (dirPath: string): Promise<boolean> => {
107eeffdb10STomasz Sapeta  const contents = await fs.readdir(dirPath);
108eeffdb10STomasz Sapeta  const results = await Promise.all(
109eeffdb10STomasz Sapeta    contents.map(async (file) => {
110eeffdb10STomasz Sapeta      const filePath = path.join(dirPath, file);
111eeffdb10STomasz Sapeta      const fileStats = await fs.lstat(filePath);
112eeffdb10STomasz Sapeta      return fileStats.isDirectory() && (await removeUponEmptyOrOnlyEmptySubdirs(filePath));
113eeffdb10STomasz Sapeta    })
114eeffdb10STomasz Sapeta  );
115eeffdb10STomasz Sapeta  const isRemovable = results.reduce((acc, current) => acc && current, true);
116eeffdb10STomasz Sapeta  if (isRemovable) {
117eeffdb10STomasz Sapeta    await fs.remove(dirPath);
118eeffdb10STomasz Sapeta  }
119eeffdb10STomasz Sapeta  return isRemovable;
120eeffdb10STomasz Sapeta};
121eeffdb10STomasz Sapeta
122eeffdb10STomasz Sapeta/**
123eeffdb10STomasz Sapeta * Prepares iOS part, mainly by renaming all files and some template word in files
124eeffdb10STomasz Sapeta * Versioning is done automatically based on package.json from JS/TS part
125eeffdb10STomasz Sapeta * @param modulePath - module directory
126eeffdb10STomasz Sapeta * @param configuration - naming configuration
127eeffdb10STomasz Sapeta */
128eeffdb10STomasz Sapetaasync function configureIOS(
129eeffdb10STomasz Sapeta  modulePath: string,
130eeffdb10STomasz Sapeta  { podName, jsPackageName, viewManager }: ModuleConfiguration
131eeffdb10STomasz Sapeta) {
132eeffdb10STomasz Sapeta  const iosPath = path.join(modulePath, 'ios');
133eeffdb10STomasz Sapeta
134eeffdb10STomasz Sapeta  // remove ViewManager from template
135eeffdb10STomasz Sapeta  if (!viewManager) {
136*74d26049SŁukasz Kosmaty    await removeFiles(iosPath, [
137eeffdb10STomasz Sapeta      `EXModuleTemplateView.h`,
138eeffdb10STomasz Sapeta      `EXModuleTemplateView.m`,
139eeffdb10STomasz Sapeta      `EXModuleTemplateViewManager.h`,
140eeffdb10STomasz Sapeta      `EXModuleTemplateViewManager.m`,
141eeffdb10STomasz Sapeta    ]);
142eeffdb10STomasz Sapeta  }
143eeffdb10STomasz Sapeta
144eeffdb10STomasz Sapeta  await renameFilesWithExtensions(
145*74d26049SŁukasz Kosmaty    iosPath,
146*74d26049SŁukasz Kosmaty    ['.swift'],
147eeffdb10STomasz Sapeta    [
148*74d26049SŁukasz Kosmaty      { from: 'ExpoModuleTemplateModule', to: `${podName}Module` },
149eeffdb10STomasz Sapeta      {
150*74d26049SŁukasz Kosmaty        from: 'ExpoModuleTemplateView',
151eeffdb10STomasz Sapeta        to: `${podName}View`,
152eeffdb10STomasz Sapeta      },
153eeffdb10STomasz Sapeta      {
154*74d26049SŁukasz Kosmaty        from: 'ExpoModuleTemplateViewManager',
155eeffdb10STomasz Sapeta        to: `${podName}ViewManager`,
156eeffdb10STomasz Sapeta      },
157eeffdb10STomasz Sapeta    ]
158eeffdb10STomasz Sapeta  );
159eeffdb10STomasz Sapeta  await renameFilesWithExtensions(
160eeffdb10STomasz Sapeta    iosPath,
161eeffdb10STomasz Sapeta    ['', '.podspec'],
162*74d26049SŁukasz Kosmaty    [{ from: 'ExpoModuleTemplate', to: `${podName}` }]
163eeffdb10STomasz Sapeta  );
164eeffdb10STomasz Sapeta  await replaceContents(iosPath, (singleFileContent) =>
165eeffdb10STomasz Sapeta    singleFileContent
166*74d26049SŁukasz Kosmaty      .replace(/ExpoModuleTemplate/g, podName)
167eeffdb10STomasz Sapeta      .replace(/ExpoModuleTemplate/g, jsPackageName)
168eeffdb10STomasz Sapeta  );
169eeffdb10STomasz Sapeta}
170eeffdb10STomasz Sapeta
171eeffdb10STomasz Sapeta/**
172eeffdb10STomasz Sapeta * Gets path to Android source base dir: android/src/main/[java|kotlin]
173eeffdb10STomasz Sapeta * Defaults to Java path if both exist
174eeffdb10STomasz Sapeta * @param androidPath path do module android/ directory
175eeffdb10STomasz Sapeta * @param flavor package flavor e.g main, test. Defaults to main
176eeffdb10STomasz Sapeta * @returns path to flavor source base directory
177eeffdb10STomasz Sapeta */
178eeffdb10STomasz Sapetafunction findAndroidSourceDir(androidPath: string, flavor: string = 'main'): string {
179eeffdb10STomasz Sapeta  const androidSrcPathBase = path.join(androidPath, 'src', flavor);
180eeffdb10STomasz Sapeta
181eeffdb10STomasz Sapeta  const javaExists = fs.pathExistsSync(path.join(androidSrcPathBase, 'java'));
182eeffdb10STomasz Sapeta  const kotlinExists = fs.pathExistsSync(path.join(androidSrcPathBase, 'kotlin'));
183eeffdb10STomasz Sapeta
184eeffdb10STomasz Sapeta  if (!javaExists && !kotlinExists) {
185eeffdb10STomasz Sapeta    throw new Error(
186eeffdb10STomasz Sapeta      `Invalid template. Android source directory not found: ${androidSrcPathBase}/[java|kotlin]`
187eeffdb10STomasz Sapeta    );
188eeffdb10STomasz Sapeta  }
189eeffdb10STomasz Sapeta
190eeffdb10STomasz Sapeta  return path.join(androidSrcPathBase, javaExists ? 'java' : 'kotlin');
191eeffdb10STomasz Sapeta}
192eeffdb10STomasz Sapeta
193eeffdb10STomasz Sapeta/**
194eeffdb10STomasz Sapeta * Finds java package name based on directory structure
195eeffdb10STomasz Sapeta * @param flavorSrcPath Path to source base directory: e.g. android/src/main/java
196eeffdb10STomasz Sapeta * @returns java package name
197eeffdb10STomasz Sapeta */
198eeffdb10STomasz Sapetafunction findTemplateAndroidPackage(flavorSrcPath: string) {
199eeffdb10STomasz Sapeta  const srcFiles = walkSync(flavorSrcPath, {
200eeffdb10STomasz Sapeta    filter: (item) => item.path.endsWith('.kt') || item.path.endsWith('.java'),
201eeffdb10STomasz Sapeta    nodir: true,
202eeffdb10STomasz Sapeta    traverseAll: true,
203eeffdb10STomasz Sapeta  });
204eeffdb10STomasz Sapeta
205eeffdb10STomasz Sapeta  if (srcFiles.length === 0) {
206eeffdb10STomasz Sapeta    throw new Error('No Android source files found in the template');
207eeffdb10STomasz Sapeta  }
208eeffdb10STomasz Sapeta
209eeffdb10STomasz Sapeta  // srcFiles[0] will always be at the most top-level of the package structure
210eeffdb10STomasz Sapeta  const packageDirNames = path.relative(flavorSrcPath, srcFiles[0].path).split('/').slice(0, -1);
211eeffdb10STomasz Sapeta
212eeffdb10STomasz Sapeta  if (packageDirNames.length === 0) {
213eeffdb10STomasz Sapeta    throw new Error('Template Android sources must be within a package.');
214eeffdb10STomasz Sapeta  }
215eeffdb10STomasz Sapeta
216eeffdb10STomasz Sapeta  return packageDirNames.join('.');
217eeffdb10STomasz Sapeta}
218eeffdb10STomasz Sapeta
219eeffdb10STomasz Sapeta/**
220eeffdb10STomasz Sapeta * Prepares Android part, mainly by renaming all files and template words in files
221eeffdb10STomasz Sapeta * Sets all versions in Gradle to 1.0.0
222eeffdb10STomasz Sapeta * @param modulePath - module directory
223eeffdb10STomasz Sapeta * @param configuration - naming configuration
224eeffdb10STomasz Sapeta */
225eeffdb10STomasz Sapetaasync function configureAndroid(
226eeffdb10STomasz Sapeta  modulePath: string,
227eeffdb10STomasz Sapeta  { javaPackage, jsPackageName, viewManager }: ModuleConfiguration
228eeffdb10STomasz Sapeta) {
229eeffdb10STomasz Sapeta  const androidPath = path.join(modulePath, 'android');
230eeffdb10STomasz Sapeta  const [, moduleName] = preparePrefixes(jsPackageName, 'Expo');
231eeffdb10STomasz Sapeta
232eeffdb10STomasz Sapeta  const androidSrcPath = findAndroidSourceDir(androidPath);
233eeffdb10STomasz Sapeta  const templateJavaPackage = findTemplateAndroidPackage(androidSrcPath);
234eeffdb10STomasz Sapeta
235eeffdb10STomasz Sapeta  const sourceFilesPath = path.join(androidSrcPath, ...templateJavaPackage.split('.'));
236eeffdb10STomasz Sapeta  const destinationFilesPath = path.join(androidSrcPath, ...javaPackage.split('.'));
237eeffdb10STomasz Sapeta
238eeffdb10STomasz Sapeta  // remove ViewManager from template
239eeffdb10STomasz Sapeta  if (!viewManager) {
240eeffdb10STomasz Sapeta    removeFiles(sourceFilesPath, [`ModuleTemplateView.kt`, `ModuleTemplateViewManager.kt`]);
241eeffdb10STomasz Sapeta
242eeffdb10STomasz Sapeta    replaceContent(path.join(sourceFilesPath, 'ModuleTemplatePackage.kt'), (packageContent) =>
243eeffdb10STomasz Sapeta      packageContent
244eeffdb10STomasz Sapeta        .replace(/(^\s+)+(^.*?){1}createViewManagers[\s\W\w]+?\}/m, '')
245eeffdb10STomasz Sapeta        .replace(/^.*ViewManager$/, '')
246eeffdb10STomasz Sapeta    );
247eeffdb10STomasz Sapeta  }
248eeffdb10STomasz Sapeta
249eeffdb10STomasz Sapeta  await fs.mkdirp(destinationFilesPath);
250eeffdb10STomasz Sapeta  await fs.copy(sourceFilesPath, destinationFilesPath);
251eeffdb10STomasz Sapeta
252eeffdb10STomasz Sapeta  // Remove leaf directory content
253eeffdb10STomasz Sapeta  await fs.remove(sourceFilesPath);
254eeffdb10STomasz Sapeta  // Cleanup all empty subdirs up to template package root dir
255eeffdb10STomasz Sapeta  await removeUponEmptyOrOnlyEmptySubdirs(
256eeffdb10STomasz Sapeta    path.join(androidSrcPath, templateJavaPackage.split('.')[0])
257eeffdb10STomasz Sapeta  );
258eeffdb10STomasz Sapeta
259eeffdb10STomasz Sapeta  // prepare tests
260eeffdb10STomasz Sapeta  if (fs.existsSync(path.resolve(androidPath, 'src', 'test'))) {
261eeffdb10STomasz Sapeta    const androidTestPath = findAndroidSourceDir(androidPath, 'test');
262eeffdb10STomasz Sapeta    const templateTestPackage = findTemplateAndroidPackage(androidTestPath);
263eeffdb10STomasz Sapeta    const testSourcePath = path.join(androidTestPath, ...templateTestPackage.split('.'));
264eeffdb10STomasz Sapeta    const testDestinationPath = path.join(androidTestPath, ...javaPackage.split('.'));
265eeffdb10STomasz Sapeta
266eeffdb10STomasz Sapeta    await fs.mkdirp(testDestinationPath);
267eeffdb10STomasz Sapeta    await fs.copy(testSourcePath, testDestinationPath);
268eeffdb10STomasz Sapeta    await fs.remove(testSourcePath);
269eeffdb10STomasz Sapeta    await removeUponEmptyOrOnlyEmptySubdirs(
270eeffdb10STomasz Sapeta      path.join(androidTestPath, templateTestPackage.split('.')[0])
271eeffdb10STomasz Sapeta    );
272eeffdb10STomasz Sapeta
273eeffdb10STomasz Sapeta    await replaceContents(testDestinationPath, (singleFileContent) =>
274eeffdb10STomasz Sapeta      singleFileContent.replace(new RegExp(templateTestPackage, 'g'), javaPackage)
275eeffdb10STomasz Sapeta    );
276eeffdb10STomasz Sapeta
277eeffdb10STomasz Sapeta    await renameFilesWithExtensions(
278eeffdb10STomasz Sapeta      testDestinationPath,
279eeffdb10STomasz Sapeta      ['.kt', '.java'],
280eeffdb10STomasz Sapeta      [{ from: 'ModuleTemplateModuleTest', to: `${moduleName}ModuleTest` }]
281eeffdb10STomasz Sapeta    );
282eeffdb10STomasz Sapeta  }
283eeffdb10STomasz Sapeta
284eeffdb10STomasz Sapeta  // Replace contents of destination files
285eeffdb10STomasz Sapeta  await replaceContents(androidPath, (singleFileContent) =>
286eeffdb10STomasz Sapeta    singleFileContent
287eeffdb10STomasz Sapeta      .replace(new RegExp(templateJavaPackage, 'g'), javaPackage)
288eeffdb10STomasz Sapeta      .replace(/ModuleTemplate/g, moduleName)
289eeffdb10STomasz Sapeta      .replace(/ExpoModuleTemplate/g, jsPackageName)
290eeffdb10STomasz Sapeta  );
291eeffdb10STomasz Sapeta  await replaceContent(path.join(androidPath, 'build.gradle'), (gradleContent) =>
292eeffdb10STomasz Sapeta    gradleContent
293eeffdb10STomasz Sapeta      .replace(/\bversion = ['"][\w.-]+['"]/, "version = '1.0.0'")
294eeffdb10STomasz Sapeta      .replace(/versionCode \d+/, 'versionCode 1')
295eeffdb10STomasz Sapeta      .replace(/versionName ['"][\w.-]+['"]/, "versionName '1.0.0'")
296eeffdb10STomasz Sapeta  );
297eeffdb10STomasz Sapeta  await renameFilesWithExtensions(
298eeffdb10STomasz Sapeta    destinationFilesPath,
299eeffdb10STomasz Sapeta    ['.kt', '.java'],
300eeffdb10STomasz Sapeta    [
301eeffdb10STomasz Sapeta      { from: 'ModuleTemplateModule', to: `${moduleName}Module` },
302eeffdb10STomasz Sapeta      { from: 'ModuleTemplatePackage', to: `${moduleName}Package` },
303eeffdb10STomasz Sapeta      { from: 'ModuleTemplateView', to: `${moduleName}View` },
304eeffdb10STomasz Sapeta      { from: 'ModuleTemplateViewManager', to: `${moduleName}ViewManager` },
305eeffdb10STomasz Sapeta    ]
306eeffdb10STomasz Sapeta  );
307eeffdb10STomasz Sapeta}
308eeffdb10STomasz Sapeta
309eeffdb10STomasz Sapeta/**
310eeffdb10STomasz Sapeta * Prepares TS part.
311eeffdb10STomasz Sapeta * @param modulePath - module directory
312eeffdb10STomasz Sapeta * @param configuration - naming configuration
313eeffdb10STomasz Sapeta */
314eeffdb10STomasz Sapetaasync function configureTS(
315eeffdb10STomasz Sapeta  modulePath: string,
316eeffdb10STomasz Sapeta  { jsPackageName, viewManager }: ModuleConfiguration
317eeffdb10STomasz Sapeta) {
318eeffdb10STomasz Sapeta  const [moduleNameWithExpoPrefix, moduleName] = preparePrefixes(jsPackageName);
319eeffdb10STomasz Sapeta
320eeffdb10STomasz Sapeta  const tsPath = path.join(modulePath, 'src');
321eeffdb10STomasz Sapeta
322eeffdb10STomasz Sapeta  // remove View Manager from template
323eeffdb10STomasz Sapeta  if (!viewManager) {
324eeffdb10STomasz Sapeta    await removeFiles(path.join(tsPath), [
325eeffdb10STomasz Sapeta      'ExpoModuleTemplateView.tsx',
326eeffdb10STomasz Sapeta      'ExpoModuleTemplateNativeView.ts',
327eeffdb10STomasz Sapeta      'ExpoModuleTemplateNativeView.web.tsx',
328eeffdb10STomasz Sapeta    ]);
329eeffdb10STomasz Sapeta    await replaceContent(path.join(tsPath, 'ModuleTemplate.ts'), (fileContent) =>
330eeffdb10STomasz Sapeta      fileContent.replace(/(^\s+)+(^.*?){1}ExpoModuleTemplateView.*$/m, '')
331eeffdb10STomasz Sapeta    );
332eeffdb10STomasz Sapeta  }
333eeffdb10STomasz Sapeta
334eeffdb10STomasz Sapeta  await renameFilesWithExtensions(
335eeffdb10STomasz Sapeta    path.join(tsPath, '__tests__'),
336eeffdb10STomasz Sapeta    ['.ts'],
337eeffdb10STomasz Sapeta    [{ from: 'ModuleTemplate-test', to: `${moduleName}-test` }]
338eeffdb10STomasz Sapeta  );
339eeffdb10STomasz Sapeta  await renameFilesWithExtensions(
340eeffdb10STomasz Sapeta    tsPath,
341eeffdb10STomasz Sapeta    ['.tsx', '.ts'],
342eeffdb10STomasz Sapeta    [
343eeffdb10STomasz Sapeta      { from: 'ExpoModuleTemplateView', to: `${moduleNameWithExpoPrefix}View` },
344eeffdb10STomasz Sapeta      { from: 'ExpoModuleTemplateNativeView', to: `${moduleNameWithExpoPrefix}NativeView` },
345eeffdb10STomasz Sapeta      { from: 'ExpoModuleTemplateNativeView.web', to: `${moduleNameWithExpoPrefix}NativeView.web` },
346eeffdb10STomasz Sapeta      { from: 'ExpoModuleTemplate', to: moduleNameWithExpoPrefix },
347eeffdb10STomasz Sapeta      { from: 'ExpoModuleTemplate.web', to: `${moduleNameWithExpoPrefix}.web` },
348eeffdb10STomasz Sapeta      { from: 'ModuleTemplate', to: moduleName },
349eeffdb10STomasz Sapeta      { from: 'ModuleTemplate.types', to: `${moduleName}.types` },
350eeffdb10STomasz Sapeta    ]
351eeffdb10STomasz Sapeta  );
352eeffdb10STomasz Sapeta
353eeffdb10STomasz Sapeta  await replaceContents(tsPath, (singleFileContent) =>
354eeffdb10STomasz Sapeta    singleFileContent
355eeffdb10STomasz Sapeta      .replace(/ExpoModuleTemplate/g, moduleNameWithExpoPrefix)
356eeffdb10STomasz Sapeta      .replace(/ModuleTemplate/g, moduleName)
357eeffdb10STomasz Sapeta  );
358eeffdb10STomasz Sapeta}
359eeffdb10STomasz Sapeta
360eeffdb10STomasz Sapeta/**
361eeffdb10STomasz Sapeta * Prepares files for npm (package.json and README.md).
362eeffdb10STomasz Sapeta * @param modulePath - module directory
363eeffdb10STomasz Sapeta * @param configuration - naming configuration
364eeffdb10STomasz Sapeta */
365eeffdb10STomasz Sapetaasync function configureNPM(
366eeffdb10STomasz Sapeta  modulePath: string,
367eeffdb10STomasz Sapeta  { npmModuleName, podName, jsPackageName }: ModuleConfiguration
368eeffdb10STomasz Sapeta) {
369eeffdb10STomasz Sapeta  const [, moduleName] = preparePrefixes(jsPackageName);
370eeffdb10STomasz Sapeta
371eeffdb10STomasz Sapeta  await replaceContent(path.join(modulePath, 'package.json'), (singleFileContent) =>
372eeffdb10STomasz Sapeta    singleFileContent
373eeffdb10STomasz Sapeta      .replace(/expo-module-template/g, npmModuleName)
374eeffdb10STomasz Sapeta      .replace(/"version": "[\w.-]+"/, '"version": "1.0.0"')
375eeffdb10STomasz Sapeta      .replace(/ExpoModuleTemplate/g, jsPackageName)
376eeffdb10STomasz Sapeta      .replace(/ModuleTemplate/g, moduleName)
377eeffdb10STomasz Sapeta  );
378eeffdb10STomasz Sapeta  await replaceContent(path.join(modulePath, 'README.md'), (readmeContent) =>
379eeffdb10STomasz Sapeta    readmeContent
380eeffdb10STomasz Sapeta      .replace(/expo-module-template/g, npmModuleName)
381eeffdb10STomasz Sapeta      .replace(/ExpoModuleTemplate/g, jsPackageName)
382eeffdb10STomasz Sapeta      .replace(/EXModuleTemplate/g, podName)
383eeffdb10STomasz Sapeta  );
384eeffdb10STomasz Sapeta}
385eeffdb10STomasz Sapeta
386eeffdb10STomasz Sapeta/**
387eeffdb10STomasz Sapeta * Configures TS, Android and iOS parts of generated module mostly by applying provided renamings.
388eeffdb10STomasz Sapeta * @param modulePath - module directory
389eeffdb10STomasz Sapeta * @param configuration - naming configuration
390eeffdb10STomasz Sapeta */
391eeffdb10STomasz Sapetaexport default async function configureModule(
392eeffdb10STomasz Sapeta  newModulePath: string,
393eeffdb10STomasz Sapeta  configuration: ModuleConfiguration
394eeffdb10STomasz Sapeta) {
395eeffdb10STomasz Sapeta  await configureNPM(newModulePath, configuration);
396eeffdb10STomasz Sapeta  await configureTS(newModulePath, configuration);
397eeffdb10STomasz Sapeta  await configureAndroid(newModulePath, configuration);
398eeffdb10STomasz Sapeta  await configureIOS(newModulePath, configuration);
399eeffdb10STomasz Sapeta}
400