import { applyPatch } from 'diff'; import { accessSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import type { PlatformString } from './types'; export const mkdir = (path: string, recursive: boolean = false) => { mkdirSync(path, { recursive, }); }; const interpolateVariables = (str: string, variables: Record): string => { const variableRegex = /\${{[A-z0-9]+}}/; let match = variableRegex.exec(str); while (match) { const variable = match[0].slice(3, -2); if (!(variable in variables)) { throw new Error( `The expo-brownfield template variable "${variable}" has no value, so the generated file would contain the literal string "undefined" and fail to build. This usually means the plugin passed an incomplete variable set for this template — report this at https://github.com/expo/expo/issues.` ); } str = str.replace(match[0], String(variables[variable])); match = variableRegex.exec(str); } return str; }; const maybeReadOverwrittenTemplate = (template: string, platform?: PlatformString): string => { try { accessSync(path.join(process.cwd(), '.brownfield-templates')); if (existsSync(path.join(process.cwd(), '.brownfield-templates', template))) { return readFileSync(path.join(process.cwd(), '.brownfield-templates', template)).toString(); } if (existsSync(path.join(process.cwd(), '.brownfield-templates', platform ?? '.', template))) { return readFileSync( path.join(process.cwd(), '.brownfield-templates', platform ?? '.', template) ).toString(); } // eslint-disable-next-line no-empty } catch {} return ''; }; const readTemplate = (template: string, platform?: PlatformString): string => { // First check if the template exists in the .brownfield-templates directory const overwrittenTemplate = maybeReadOverwrittenTemplate(template, platform); if (overwrittenTemplate) { return overwrittenTemplate; } // If not use the default template const templatesPath = path.join(__filename, '../../..', 'templates', platform ?? '.'); const templatePath = path.join(templatesPath, template); if (!existsSync(templatePath)) { throw new Error(`Template ${template} doesn't exist at ${templatePath}`); } return readFileSync(templatePath).toString(); }; const createFileFromTemplateInternal = ( template: string, at: string, dest: string, platform?: PlatformString, variables?: Record ) => { let templateContents = readTemplate(template, platform); if (variables) { templateContents = interpolateVariables(templateContents, variables); } const destPath = path.join(at, dest); writeFileSync(destPath, templateContents); }; export const createFileFromTemplate = ( template: string, at: string, platform?: PlatformString, variables?: Record ) => { createFileFromTemplateInternal(template, at, template, platform, variables); }; export const createFileFromTemplateAs = ( template: string, at: string, as: string, platform?: PlatformString, variables?: Record ) => { createFileFromTemplateInternal(template, at, as, platform, variables); }; export const readFromTemplate = ( template: string, platform?: PlatformString, variables?: Record ): string => { let templateContents = readTemplate(template, platform); if (variables) { templateContents = interpolateVariables(templateContents, variables); } return templateContents; }; /** * Applies a unified diff patch to a file. * @param patchFile - The name of the patch file in the patches directory * @param targetFilePath - The absolute path to the file to patch */ export const applyPatchToFile = (patchFile: string, targetFilePath: string) => { const patchPath = path.join(__filename, '../../..', 'templates', 'patches', patchFile); if (!existsSync(patchPath)) { throw new Error(`Patch file ${patchFile} doesn't exist at ${patchPath}`); } if (!existsSync(targetFilePath)) { throw new Error(`Target file doesn't exist at ${targetFilePath}`); } const patchContent = readFileSync(patchPath, 'utf-8'); const originalContent = readFileSync(targetFilePath, 'utf-8'); const patchedContent = applyPatch(originalContent, patchContent); if (patchedContent === false) { throw new Error(`Failed to apply patch ${patchFile} to ${targetFilePath}`); } writeFileSync(targetFilePath, patchedContent); };