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