1/**
2 * These are the versioned first-party plugins with some of the future third-party plugins mixed in for legacy support.
3 */
4import {
5  AndroidConfig,
6  ConfigPlugin,
7  IOSConfig,
8  StaticPlugin,
9  withPlugins,
10  withStaticPlugin,
11} from '@expo/config-plugins';
12import { ExpoConfig } from '@expo/config-types';
13import Debug from 'debug';
14
15import { shouldSkipAutoPlugin } from '../getAutolinkedPackages';
16import { withAndroidIcons } from './icons/withAndroidIcons';
17import { withIosIcons } from './icons/withIosIcons';
18import withAdMob from './unversioned/expo-ads-admob/expo-ads-admob';
19import withAppleAuthentication from './unversioned/expo-apple-authentication';
20import withBranch from './unversioned/expo-branch/expo-branch';
21import withContacts from './unversioned/expo-contacts';
22import withDocumentPicker from './unversioned/expo-document-picker';
23import withNavigationBar from './unversioned/expo-navigation-bar/expo-navigation-bar';
24import withNotifications from './unversioned/expo-notifications/expo-notifications';
25import withSplashScreen from './unversioned/expo-splash-screen/expo-splash-screen';
26import withSystemUI from './unversioned/expo-system-ui/expo-system-ui';
27import withUpdates from './unversioned/expo-updates';
28import withMaps from './unversioned/react-native-maps';
29
30const debug = Debug('expo:prebuild-config');
31
32/**
33 * Config plugin to apply all of the custom Expo iOS config plugins we support by default.
34 * TODO: In the future most of this should go into versioned packages like expo-updates, etc...
35 */
36export const withIosExpoPlugins: ConfigPlugin<{
37  bundleIdentifier: string;
38}> = (config, { bundleIdentifier }) => {
39  // Set the bundle ID ahead of time.
40  if (!config.ios) config.ios = {};
41  config.ios.bundleIdentifier = bundleIdentifier;
42
43  return withPlugins(config, [
44    [IOSConfig.BundleIdentifier.withBundleIdentifier, { bundleIdentifier }],
45    IOSConfig.Swift.withSwiftBridgingHeader,
46    IOSConfig.Swift.withNoopSwiftFile,
47    IOSConfig.Google.withGoogle,
48    IOSConfig.Name.withDisplayName,
49    IOSConfig.Name.withProductName,
50    IOSConfig.Orientation.withOrientation,
51    IOSConfig.RequiresFullScreen.withRequiresFullScreen,
52    IOSConfig.Scheme.withScheme,
53    IOSConfig.UsesNonExemptEncryption.withUsesNonExemptEncryption,
54    IOSConfig.Version.withBuildNumber,
55    IOSConfig.Version.withVersion,
56    IOSConfig.Google.withGoogleServicesFile,
57    IOSConfig.BuildProperties.withJsEnginePodfileProps,
58    // Entitlements
59    IOSConfig.Entitlements.withAssociatedDomains,
60    // XcodeProject
61    IOSConfig.DeviceFamily.withDeviceFamily,
62    IOSConfig.Bitcode.withBitcode,
63    IOSConfig.Locales.withLocales,
64    // Dangerous
65    withIosIcons,
66  ]);
67};
68
69/**
70 * Config plugin to apply all of the custom Expo Android config plugins we support by default.
71 * TODO: In the future most of this should go into versioned packages like expo-updates, etc...
72 */
73export const withAndroidExpoPlugins: ConfigPlugin<{
74  package: string;
75}> = (config, props) => {
76  // Set the package name ahead of time.
77  if (!config.android) config.android = {};
78  config.android.package = props.package;
79
80  return withPlugins(config, [
81    // gradle.properties
82    AndroidConfig.BuildProperties.withJsEngineGradleProps,
83
84    // settings.gradle
85    AndroidConfig.Name.withNameSettingsGradle,
86
87    // project build.gradle
88    AndroidConfig.GoogleServices.withClassPath,
89
90    // app/build.gradle
91    AndroidConfig.GoogleServices.withApplyPlugin,
92    AndroidConfig.Package.withPackageGradle,
93    AndroidConfig.Version.withVersion,
94
95    // AndroidManifest.xml
96    AndroidConfig.Package.withPackageManifest,
97    AndroidConfig.AllowBackup.withAllowBackup,
98    AndroidConfig.WindowSoftInputMode.withWindowSoftInputMode,
99    // Note: The withAndroidIntentFilters plugin must appear before the withScheme
100    // plugin or withScheme will override the output of withAndroidIntentFilters.
101    AndroidConfig.IntentFilters.withAndroidIntentFilters,
102    AndroidConfig.Scheme.withScheme,
103    AndroidConfig.Orientation.withOrientation,
104    AndroidConfig.Permissions.withInternalBlockedPermissions,
105    AndroidConfig.Permissions.withPermissions,
106
107    // strings.xml
108    AndroidConfig.Name.withName,
109
110    // Dangerous -- these plugins run in reverse order.
111    AndroidConfig.GoogleServices.withGoogleServicesFile,
112
113    // Modify colors.xml and styles.xml
114    AndroidConfig.StatusBar.withStatusBar,
115    AndroidConfig.PrimaryColor.withPrimaryColor,
116
117    withAndroidIcons,
118    // If we renamed the package, we should also move it around and rename it in source files
119    // Added last to ensure this plugin runs first. Out of tree solutions will mistakenly resolve the package incorrectly otherwise.
120    AndroidConfig.Package.withPackageRefactor,
121  ]);
122};
123
124// Must keep in sync with `withVersionedExpoSDKPlugins`
125const versionedExpoSDKPackages: string[] = [
126  'react-native-maps',
127  'expo-ads-admob',
128  'expo-apple-authentication',
129  'expo-contacts',
130  'expo-notifications',
131  'expo-updates',
132  'expo-branch',
133  'expo-navigation-bar',
134  'expo-document-picker',
135  'expo-splash-screen',
136  'expo-system-ui',
137];
138
139export const withVersionedExpoSDKPlugins: ConfigPlugin<{ expoUsername: string | null }> = (
140  config,
141  { expoUsername }
142) => {
143  return withPlugins(config, [
144    withMaps,
145    withAdMob,
146    withAppleAuthentication,
147    withContacts,
148    withNotifications,
149    [withUpdates, { expoUsername }],
150    withBranch,
151    withDocumentPicker,
152    // System UI must come before splash screen as they overlap
153    // and splash screen will warn about conflicting rules.
154    withSystemUI,
155    withSplashScreen,
156    withNavigationBar,
157  ]);
158};
159
160export function getAutoPlugins() {
161  return versionedExpoSDKPackages.concat(legacyExpoPlugins).concat(expoManagedVersionedPlugins);
162}
163
164export function getLegacyExpoPlugins() {
165  return legacyExpoPlugins;
166}
167
168// Expo managed packages that require extra update.
169// These get applied automatically to create parity with expo build in eas build.
170const legacyExpoPlugins = [
171  'expo-app-auth',
172  'expo-av',
173  'expo-background-fetch',
174  'expo-barcode-scanner',
175  'expo-brightness',
176  'expo-calendar',
177  'expo-camera',
178  'expo-cellular',
179  'expo-dev-menu',
180  'expo-dev-launcher',
181  'expo-dev-client',
182  'expo-image-picker',
183  'expo-file-system',
184  'expo-location',
185  'expo-media-library',
186  'expo-screen-orientation',
187  'expo-sensors',
188  'expo-task-manager',
189  'expo-local-authentication',
190];
191
192// Plugins that need to be automatically applied, but also get applied by expo-cli if the versioned plugin isn't available.
193// These are split up because the user doesn't need to be prompted to setup these packages.
194const expoManagedVersionedPlugins = [
195  'expo-firebase-analytics',
196  'expo-firebase-core',
197  'expo-google-sign-in',
198];
199
200const withOptionalLegacyPlugins: ConfigPlugin<(StaticPlugin | string)[]> = (config, plugins) => {
201  return plugins.reduce((prev, plugin) => {
202    if (shouldSkipAutoPlugin(config, plugin)) {
203      debug('Skipping unlinked auto plugin:', plugin);
204      return prev;
205    }
206
207    return withStaticPlugin(prev, {
208      // hide errors
209      _isLegacyPlugin: true,
210      plugin,
211      // If a plugin doesn't exist, do nothing.
212      fallback: (config) => config,
213    });
214  }, config);
215};
216
217export function withLegacyExpoPlugins(config: ExpoConfig) {
218  return withOptionalLegacyPlugins(config, [
219    ...new Set(expoManagedVersionedPlugins.concat(legacyExpoPlugins)),
220  ]);
221}
222