xref: /expo/tools/src/Transforms.ts (revision ca5a2fa2)
1import fs from 'fs-extra';
2import minimatch from 'minimatch';
3import path from 'path';
4
5import {
6  CopyFileOptions,
7  CopyFileResult,
8  RawTransform,
9  ReplaceTransform,
10  StringTransform,
11} from './Transforms.types';
12import { arrayize } from './Utils';
13
14export * from './Transforms.types';
15
16function isRawTransform(transform: any): transform is RawTransform {
17  return transform.transform;
18}
19
20function isReplaceTransform(transform: any): transform is ReplaceTransform {
21  return transform.find !== undefined && transform.replaceWith !== undefined;
22}
23
24/**
25 * Transforms input string according to the given transform rules.
26 */
27export function transformString(
28  input: string,
29  transforms: StringTransform[] | null | undefined
30): string {
31  if (!transforms) {
32    return input;
33  }
34  return transforms.reduce((acc, transform) => {
35    if (isRawTransform(transform)) {
36      return transform.transform(acc);
37    } else if (isReplaceTransform(transform)) {
38      const { find, replaceWith } = transform;
39      // @ts-ignore @tsapeta: TS gets crazy on `replaceWith` being a function.
40      return acc.replace(find, replaceWith);
41    }
42    throw new Error(`Unknown transform type`);
43  }, input);
44}
45
46/**
47 * Transforms file's content in-place.
48 */
49export async function transformFileAsync(
50  filePath: string,
51  transforms: StringTransform[] | null | undefined
52): Promise<void> {
53  const content = await fs.readFile(filePath, 'utf8');
54  await fs.outputFile(filePath, transformString(content, transforms));
55}
56
57/**
58 * Copies a file from source directory to target directory with transformed relative path and content.
59 */
60export async function copyFileWithTransformsAsync(
61  options: CopyFileOptions
62): Promise<CopyFileResult> {
63  const { sourceFile, sourceDirectory, targetDirectory, transforms } = options;
64  const sourcePath = path.join(sourceDirectory, sourceFile);
65
66  // Transform the target path according to rename rules.
67  const targetFile = transformString(sourceFile, transforms.path);
68  const targetPath = path.join(targetDirectory, targetFile);
69
70  // Filter out transforms that don't match paths patterns.
71  const filteredContentTransforms =
72    transforms.content?.filter(
73      ({ paths }) =>
74        !paths ||
75        arrayize(paths).some((pattern) => minimatch(sourceFile, pattern, { matchBase: true }))
76    ) ?? [];
77
78  // Transform source content.
79  const content = transformString(await fs.readFile(sourcePath, 'utf8'), filteredContentTransforms);
80
81  // Save transformed source file at renamed target path.
82  await fs.outputFile(targetPath, content);
83
84  return {
85    content,
86    targetFile,
87  };
88}
89