xref: /expo/tools/src/Transforms.ts (revision eff6a5bc)
1import fs from 'fs-extra';
2import minimatch from 'minimatch';
3import path from 'path';
4
5import { CopyFileOptions, CopyFileResult, StringTransform } from './Transforms.types';
6import { arrayize } from './Utils';
7
8export * from './Transforms.types';
9
10/**
11 * Transforms input string according to the given transform rules.
12 */
13export function transformString(
14  input: string,
15  transforms: StringTransform[] | null | undefined
16): string {
17  if (!transforms) {
18    return input;
19  }
20  return transforms.reduce(
21    // @ts-ignore @tsapeta: TS gets crazy on `replaceWith` being a function.
22    (acc, { find, replaceWith }) => acc.replace(find, replaceWith),
23    input
24  );
25}
26
27/**
28 * Copies a file from source directory to target directory with transformed relative path and content.
29 */
30export async function copyFileWithTransformsAsync(
31  options: CopyFileOptions
32): Promise<CopyFileResult> {
33  const { sourceFile, sourceDirectory, targetDirectory, transforms } = options;
34  const sourcePath = path.join(sourceDirectory, sourceFile);
35
36  // Transform the target path according to rename rules.
37  const targetFile = transformString(sourceFile, transforms.path);
38  const targetPath = path.join(targetDirectory, targetFile);
39
40  // Filter out transforms that don't match paths patterns.
41  const filteredContentTransforms =
42    transforms.content?.filter(
43      ({ paths }) =>
44        !paths ||
45        arrayize(paths).some((pattern) => minimatch(sourceFile, pattern, { matchBase: true }))
46    ) ?? [];
47
48  // Transform source content.
49  const content = transformString(await fs.readFile(sourcePath, 'utf8'), filteredContentTransforms);
50
51  // Save transformed source file at renamed target path.
52  await fs.outputFile(targetPath, content);
53
54  return {
55    content,
56    targetFile,
57  };
58}
59