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