109bb6093SEvan Baconimport { ExpoConfig } from '@expo/config';
209bb6093SEvan Baconimport {
309bb6093SEvan Bacon  normalizeStaticPlugin,
409bb6093SEvan Bacon  resolveConfigPluginFunctionWithInfo,
509bb6093SEvan Bacon} from '@expo/config-plugins/build/utils/plugin-resolver';
609bb6093SEvan Baconimport { getAutoPlugins } from '@expo/prebuild-config';
709bb6093SEvan Bacon
809bb6093SEvan Baconimport { attemptAddingPluginsAsync } from '../../utils/modifyConfigPlugins';
909bb6093SEvan Bacon
10*474a7a4bSEvan Baconconst debug = require('debug')('expo:install:config-plugins') as typeof console.log;
11*474a7a4bSEvan Bacon
1209bb6093SEvan Baconconst AUTO_PLUGINS = getAutoPlugins();
1309bb6093SEvan Bacon
1409bb6093SEvan Bacon/**
1509bb6093SEvan Bacon * Resolve if a package has a config plugin.
1609bb6093SEvan Bacon * For sanity, we'll only support config plugins that use the `app.config.js` entry file,
1709bb6093SEvan Bacon * this is because a package like `lodash` could be a "valid" config plugin and break the prebuild process.
1809bb6093SEvan Bacon *
1909bb6093SEvan Bacon * @param projectRoot
2009bb6093SEvan Bacon * @param packageName
2109bb6093SEvan Bacon * @returns
2209bb6093SEvan Bacon */
2309bb6093SEvan Baconfunction packageHasConfigPlugin(projectRoot: string, packageName: string) {
2409bb6093SEvan Bacon  try {
2509bb6093SEvan Bacon    const info = resolveConfigPluginFunctionWithInfo(projectRoot, packageName);
2609bb6093SEvan Bacon    if (info.isPluginFile) {
2709bb6093SEvan Bacon      return info.plugin;
2809bb6093SEvan Bacon    }
2909bb6093SEvan Bacon  } catch {}
3009bb6093SEvan Bacon  return false;
3109bb6093SEvan Bacon}
3209bb6093SEvan Bacon
3309bb6093SEvan Bacon/**
3409bb6093SEvan Bacon * Get a list of plugins that were are supplied as string module IDs.
3509bb6093SEvan Bacon * @example
3609bb6093SEvan Bacon * ```json
3709bb6093SEvan Bacon * {
3809bb6093SEvan Bacon *   "plugins": [
3909bb6093SEvan Bacon *     "expo-camera",
4009bb6093SEvan Bacon *     ["react-native-firebase", ...]
4109bb6093SEvan Bacon *   ]
4209bb6093SEvan Bacon * }
4309bb6093SEvan Bacon * ```
4409bb6093SEvan Bacon *   ↓ ↓ ↓ ↓ ↓ ↓
4509bb6093SEvan Bacon *
4609bb6093SEvan Bacon * `['expo-camera', 'react-native-firebase']`
4709bb6093SEvan Bacon *
4809bb6093SEvan Bacon */
4909bb6093SEvan Baconexport function getNamedPlugins(plugins: NonNullable<ExpoConfig['plugins']>): string[] {
5009bb6093SEvan Bacon  const namedPlugins: string[] = [];
5109bb6093SEvan Bacon  for (const plugin of plugins) {
5209bb6093SEvan Bacon    try {
5309bb6093SEvan Bacon      // @ts-ignore
5409bb6093SEvan Bacon      const [normal] = normalizeStaticPlugin(plugin);
5509bb6093SEvan Bacon      if (typeof normal === 'string') {
5609bb6093SEvan Bacon        namedPlugins.push(normal);
5709bb6093SEvan Bacon      }
5809bb6093SEvan Bacon    } catch {
5909bb6093SEvan Bacon      // ignore assertions
6009bb6093SEvan Bacon    }
6109bb6093SEvan Bacon  }
6209bb6093SEvan Bacon  return namedPlugins;
6309bb6093SEvan Bacon}
6409bb6093SEvan Bacon
6509bb6093SEvan Bacon/** Attempts to ensure that non-auto plugins are added to the `app.json` `plugins` array when modules with Expo Config Plugins are installed. */
6609bb6093SEvan Baconexport async function autoAddConfigPluginsAsync(
6709bb6093SEvan Bacon  projectRoot: string,
6809bb6093SEvan Bacon  exp: Pick<ExpoConfig, 'plugins'>,
6909bb6093SEvan Bacon  packages: string[]
7009bb6093SEvan Bacon) {
71*474a7a4bSEvan Bacon  debug('Checking config plugins...');
7209bb6093SEvan Bacon
7309bb6093SEvan Bacon  const currentPlugins = exp.plugins || [];
7409bb6093SEvan Bacon  const normalized = getNamedPlugins(currentPlugins);
7509bb6093SEvan Bacon
76*474a7a4bSEvan Bacon  debug(`Existing plugins: ${normalized.join(', ')}`);
7709bb6093SEvan Bacon
7809bb6093SEvan Bacon  const plugins = packages.filter((pkg) => {
7909bb6093SEvan Bacon    if (normalized.includes(pkg)) {
8009bb6093SEvan Bacon      // already included in plugins array
8109bb6093SEvan Bacon      return false;
8209bb6093SEvan Bacon    }
8309bb6093SEvan Bacon    // Check if the package has a valid plugin. Must be a well-made plugin for it to work with this.
8409bb6093SEvan Bacon    const plugin = packageHasConfigPlugin(projectRoot, pkg);
8509bb6093SEvan Bacon
86*474a7a4bSEvan Bacon    debug(`Package "${pkg}" has plugin: ${!!plugin}` + (plugin ? ` (args: ${plugin.length})` : ''));
8709bb6093SEvan Bacon
8809bb6093SEvan Bacon    if (AUTO_PLUGINS.includes(pkg)) {
89*474a7a4bSEvan Bacon      debug(`Package "${pkg}" is an auto plugin, skipping...`);
9009bb6093SEvan Bacon      return false;
9109bb6093SEvan Bacon    }
9209bb6093SEvan Bacon
9309bb6093SEvan Bacon    return !!plugin;
9409bb6093SEvan Bacon  });
9509bb6093SEvan Bacon
9609bb6093SEvan Bacon  await attemptAddingPluginsAsync(projectRoot, exp, plugins);
9709bb6093SEvan Bacon}
98