Skip to content

Instantly share code, notes, and snippets.

@GollyJer
Last active December 9, 2024 14:15
Show Gist options
  • Save GollyJer/9668ac1125eaad3e6d8cc3309867cf9d to your computer and use it in GitHub Desktop.
Save GollyJer/9668ac1125eaad3e6d8cc3309867cf9d to your computer and use it in GitHub Desktop.
app.config.ts
import { ConfigContext } from '@expo/config';
// eslint-disable-next-line no-console
// console.log('ENV', process.env);
// expands type in the vscode tooltip.
type ExpandTooltip<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
const EXPO_SDK = '51';
const BINARY_ITERATION_FOR_SDK = '1'; // start with 1. CAN NOT BE 0.
const OTA_ITERATION_FOR_BINARY = '1'; // start with 1. CAN NOT BE 0.
const appName = '';
const owner = '';
const scheme = ''; // supptrack:// URLs will open the app when tapped.
const appStoreUniqueId = `${owner}.${scheme}.app`;
const expoProjectSlug = `${scheme}-app`;
const expoProjectId = '';
const updateUrl = `https://u.expo.dev/${expoProjectId}`;
const icons = {
android: {
icon: './assets/images/supptrack-icon-android.png',
adaptiveIcon: {
foregroundImage: './assets/images/supptrack-adaptive-icon-android.png',
backgroundColor: '#000000',
monochromeImage: './assets/images/supptrack-adaptive-icon-monochrome-android.png',
},
dev: {
icon: './assets/images/supptrack-icon-dev-android.png',
adaptiveIcon: {
foregroundImage: './assets/images/supptrack-adaptive-icon-android-dev.png',
backgroundColor: '#023F05',
monochromeImage: './assets/images/supptrack-adaptive-icon-monochrome-android-dev.png',
},
},
staging: {
icon: './assets/images/supptrack-icon-staging-android.png',
adaptiveIcon: {
foregroundImage: './assets/images/supptrack-adaptive-icon-android-staging.png',
backgroundColor: '#C27025',
monochromeImage: './assets/images/supptrack-adaptive-icon-monochrome-android-staging.png',
},
},
},
ios: {
icon: './assets/images/supptrack-icon-ios.png',
dev: {
icon: './assets/images/supptrack-icon-dev-android.png',
},
staging: {
icon: './assets/images/supptrack-icon-staging-android.png',
},
},
};
const splashScreens = {
mobile: './assets/images/supptrack-splash.png',
tablet: './assets/images/supptrack-splash-tablet.png',
};
const backgroundColor = '#000000';
// A binary release is required when any non-javascript code is added to the build. This happens
// with every Expo SDK release and in between SDK releases when npm packages that require native
// code are added to the build.
//
// The first build for an Expo SDK will be <sdk_number>.1.1
// We iterate the second number for every binary build on the same SDK.
// We identify this binary via the `runtimeVersion` key and when the code is built we
// create an Expo `--release-branch` with the same identifier (via `eas.json`).
//
// This allows us to push OTA, javascript only, updates to specific binaries.
const runtimeVersion = `${EXPO_SDK}.${BINARY_ITERATION_FOR_SDK}`;
// Every binary released is identified by the runtimeVersion.
// appVersion represents the full set of code, native + javascript, a user has on their device.
// We iterate appVersion for every OTA javascript push to a binary and show this value inside the
// app via the `extra"`key to trigger and display the WhatsNew modal.
const appVersion = `${EXPO_SDK}.${BINARY_ITERATION_FOR_SDK}.${OTA_ITERATION_FOR_BINARY}`;
// === For iOS binaries built with EAS CLI ===
// Version is our runtimeVersion/release-channel.
// Build is our appVersion.
// ===========================================
// For each iOS release TestFlight has a "version" and groups "builds" underneath "version".
// iOS buildNumber is a binary identifier that must change with every binary build.
// https://diigo.com/0mo110
// Expo sets the "version" to the top level version field in this config and we use our appversion
// as an iterator for the build number.
const iosBuildNumber = appVersion;
// === For Android binaries built with EAS CLI ===
// Version is our runtimeVersoin/ReleaseChannel.
// versionCode is our androidVersionCode.
// ===========================================
// Android versionCode is a binary identifier that must increase with every binary build.
// The greatest value Google Play allows for versionCode is 2100000000.
// https://diigo.com/0mo0s4
// The following generates an incremented number every 10 seconds starting on app release date and
// works for 500 years.
const appInitUnixTime = Math.floor(new Date(2023, 2, 10).getTime() / 1000); // March is 2 because months are 0-indexed
const currentUnixTime = Math.floor(Date.now() / 1000);
const androidVersionCode = Math.round((currentUnixTime - appInitUnixTime) / 10);
const android: ExpandTooltip<ConfigContext['config']['android']> = {
package: appStoreUniqueId,
versionCode: androidVersionCode,
icon: icons.android.icon,
adaptiveIcon: {
foregroundImage: icons.android.adaptiveIcon.foregroundImage,
backgroundColor: icons.android.adaptiveIcon.backgroundColor,
},
googleServicesFile: process.env.PROD_FIREBASE_ANDROID_CONFIG,
splash: {
backgroundColor,
image: splashScreens.mobile,
},
permissions: ['com.android.vending.BILLING'],
};
const ios: ExpandTooltip<ConfigContext['config']['ios']> = {
buildNumber: iosBuildNumber,
bundleIdentifier: appStoreUniqueId,
icon: icons.ios.icon,
splash: {
backgroundColor,
image: splashScreens.mobile,
tabletImage: splashScreens.tablet,
},
googleServicesFile: process.env.PROD_FIREBASE_IOS_CONFIG,
supportsTablet: true,
config: {
usesNonExemptEncryption: false,
},
infoPlist: {
UIBackgroundModes: ['remote-notification'],
},
entitlements: {
'aps-environment': 'production',
},
};
const initialConfig: ExpandTooltip<ConfigContext['config']> = {
experiments: {
tsconfigPaths: true,
},
name: appName,
notification: {
icon: './assets/images/notification-icon-android.png',
},
plugins: [
'expo-font',
'expo-asset',
'./plugins/withFirebaseBuildErrorFix_iOS.cjs',
'./plugins/withShakeIntegration_iOS.cjs',
'./plugins/withChatNotificationMetaData_Android.cjs',
'react-native-edge-to-edge',
'@react-native-firebase/app',
'@react-native-firebase/app-check',
'@react-native-firebase/auth',
[
'expo-build-properties',
{
ios: {
useFrameworks: 'static',
},
android: {
minSdkVersion: 24,
},
},
],
[
'react-native-vision-camera',
{
cameraPermissionText: 'This will allow you to scan product bar codes.',
enableCodeScanner: true,
enableLocation: false,
},
],
[
'react-native-permissions',
{
iosPermissions: [
// 'AppTrackingTransparency',
// 'Bluetooth',
// 'Calendars',
// 'CalendarsWriteOnly',
// 'Camera', // NOTE: react-native-vision-camera is handling this.
// 'Contacts',
// 'FaceID',
// 'LocationAccuracy',
// 'LocationAlways',
// 'LocationWhenInUse',
// 'MediaLibrary',
// 'Microphone',
// 'Motion',
'Notifications',
// 'PhotoLibrary',
// 'PhotoLibraryAddOnly',
// 'Reminders',
// 'Siri',
// 'SpeechRecognition',
// 'StoreKit',
],
},
],
],
slug: expoProjectSlug,
scheme,
android,
ios,
extra: {
appVersion,
eas: { projectId: expoProjectId },
},
assetBundlePatterns: ['assets/images/*'],
backgroundColor,
primaryColor: backgroundColor,
icon: icons.android.icon,
jsEngine: 'hermes',
orientation: 'portrait',
owner,
platforms: ['ios', 'android'],
privacy: 'unlisted',
splash: { resizeMode: 'cover' },
updates: {
enabled: true,
fallbackToCacheTimeout: 10_000,
checkAutomatically: 'ON_LOAD',
url: updateUrl,
requestHeaders: {
'expo-channel-name': runtimeVersion,
},
},
userInterfaceStyle: 'automatic',
runtimeVersion: { policy: 'appVersion' },
version: runtimeVersion, // This is the "Version" number in both app stores.
};
const config = () => {
let finalConfig = initialConfig;
if (process.env.APP_ENV === 'dev') {
finalConfig = {
...initialConfig,
plugins: [...(initialConfig.plugins ?? [])],
android: {
...initialConfig.android,
package: `dev.${initialConfig.android?.package}`,
icon: icons.android.dev.icon,
adaptiveIcon: {
...initialConfig.android?.adaptiveIcon,
foregroundImage: icons.android.dev.adaptiveIcon.foregroundImage,
backgroundColor: icons.android.dev.adaptiveIcon.backgroundColor,
monochromeImage: icons.android.dev.adaptiveIcon.monochromeImage,
},
googleServicesFile: process.env.DEV_FIREBASE_ANDROID_CONFIG,
},
ios: {
...initialConfig.ios,
bundleIdentifier: `dev.${initialConfig.ios?.bundleIdentifier}`,
icon: icons.ios.dev.icon,
googleServicesFile: process.env.DEV_FIREBASE_IOS_CONFIG,
entitlements: {
'aps-environment': 'development',
},
},
};
}
if (process.env.APP_ENV === 'staging') {
finalConfig = {
...initialConfig,
plugins: [...(initialConfig.plugins ?? [])],
android: {
...initialConfig.android,
package: `staging.${initialConfig.android?.package}`,
icon: icons.android.staging.icon,
adaptiveIcon: {
...initialConfig.android?.adaptiveIcon,
foregroundImage: icons.android.staging.adaptiveIcon.foregroundImage,
backgroundColor: icons.android.staging.adaptiveIcon.backgroundColor,
monochromeImage: icons.android.staging.adaptiveIcon.monochromeImage,
},
googleServicesFile: process.env.STAGING_FIREBASE_ANDROID_CONFIG,
},
ios: {
...initialConfig.ios,
bundleIdentifier: `staging.${initialConfig.ios?.bundleIdentifier}`,
icon: icons.ios.staging.icon,
googleServicesFile: process.env.STAGING_FIREBASE_IOS_CONFIG,
entitlements: {
'aps-environment': 'development',
},
},
};
}
return {
expo: {
...finalConfig,
},
};
};
// Expo requires default export of app.config.ts
// eslint-disable-next-line import/no-default-export
export default config;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment