1import { ExpoConfig } from '@expo/config'; 2import { 3 normalizeStaticPlugin, 4 resolveConfigPluginFunctionWithInfo, 5} from '@expo/config-plugins/build/utils/plugin-resolver'; 6import { getAutoPlugins } from '@expo/prebuild-config'; 7 8import * as Log from '../../log'; 9import { attemptAddingPluginsAsync } from '../../utils/modifyConfigPlugins'; 10 11const AUTO_PLUGINS = getAutoPlugins(); 12 13/** 14 * Resolve if a package has a config plugin. 15 * For sanity, we'll only support config plugins that use the `app.config.js` entry file, 16 * this is because a package like `lodash` could be a "valid" config plugin and break the prebuild process. 17 * 18 * @param projectRoot 19 * @param packageName 20 * @returns 21 */ 22function packageHasConfigPlugin(projectRoot: string, packageName: string) { 23 try { 24 const info = resolveConfigPluginFunctionWithInfo(projectRoot, packageName); 25 if (info.isPluginFile) { 26 return info.plugin; 27 } 28 } catch {} 29 return false; 30} 31 32/** 33 * Get a list of plugins that were are supplied as string module IDs. 34 * @example 35 * ```json 36 * { 37 * "plugins": [ 38 * "expo-camera", 39 * ["react-native-firebase", ...] 40 * ] 41 * } 42 * ``` 43 * ↓ ↓ ↓ ↓ ↓ ↓ 44 * 45 * `['expo-camera', 'react-native-firebase']` 46 * 47 */ 48export function getNamedPlugins(plugins: NonNullable<ExpoConfig['plugins']>): string[] { 49 const namedPlugins: string[] = []; 50 for (const plugin of plugins) { 51 try { 52 // @ts-ignore 53 const [normal] = normalizeStaticPlugin(plugin); 54 if (typeof normal === 'string') { 55 namedPlugins.push(normal); 56 } 57 } catch { 58 // ignore assertions 59 } 60 } 61 return namedPlugins; 62} 63 64/** Attempts to ensure that non-auto plugins are added to the `app.json` `plugins` array when modules with Expo Config Plugins are installed. */ 65export async function autoAddConfigPluginsAsync( 66 projectRoot: string, 67 exp: Pick<ExpoConfig, 'plugins'>, 68 packages: string[] 69) { 70 Log.debug('Checking config plugins...'); 71 72 const currentPlugins = exp.plugins || []; 73 const normalized = getNamedPlugins(currentPlugins); 74 75 Log.debug(`Existing plugins: ${normalized.join(', ')}`); 76 77 const plugins = packages.filter((pkg) => { 78 if (normalized.includes(pkg)) { 79 // already included in plugins array 80 return false; 81 } 82 // Check if the package has a valid plugin. Must be a well-made plugin for it to work with this. 83 const plugin = packageHasConfigPlugin(projectRoot, pkg); 84 85 Log.debug( 86 `Package "${pkg}" has plugin: ${!!plugin}` + (plugin ? ` (args: ${plugin.length})` : '') 87 ); 88 89 if (AUTO_PLUGINS.includes(pkg)) { 90 Log.debug(`Package "${pkg}" is an auto plugin, skipping...`); 91 return false; 92 } 93 94 return !!plugin; 95 }); 96 97 await attemptAddingPluginsAsync(projectRoot, exp, plugins); 98} 99