Matthew Thompson

Matthew Thompson

Software Architect | Craftsman | Mentor

Manage Environments with EAS App Config

Published on August 26, 2022

eas.json

{
  "cli": {
    "version": ">= 16.4.0",
    "appVersionSource": "remote"
  },
  "build": {
    "base": {
      "node": "20.11.0"
    },
    "development": {
      "extends": "base",
      "environment": "development",
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "simulator": true
      },
      "env": {
        "BUILD_ENV": "development",
        "API_URL": "https://my.ngrok.app"
      }
    },
    "staging": {
      "extends": "base",
      "environment": "production",
      "distribution": "internal",
      "ios": {
        "simulator": false
      },
      "env": {
        "BUILD_ENV": "staging",
        "API_URL": "https://api-staging.com"
      }
    },
    "production": {
      "extends": "base",
      "environment": "production",
      "autoIncrement": true,
      "ios": {
        "simulator": false
      },
      "env": {
        "BUILD_ENV": "production",
        "API_URL": "https://api-production.com"
      }
    }
  },
  "submit": {
    "staging": {
      "ios": {
        "bundleIdentifier": "com.staging.my-identifier.app"
      }
    },
    "production": {
      "ios": {
        "bundleIdentifier": "com.my-identifier.app"
      }
    }
  }
}

app.config.ts (Sample)

import type { ConfigContext, ExpoConfig } from "expo/config";

type ProfileConfig = ExpoConfig & {
  name: string;
  slug: string;
  scheme: string;
  androidBundleIdentifier: string;
  iosBundleIdentifier: string;
  icon: string;
  splash_url: string;
  projectId?: string;
};

const getAppConfig = (): ProfileConfig => {
  const buildEnvironment = process.env.BUILD_ENV;

  if (buildEnvironment === "production") {
    return {
      name: "my-app-production",
      slug: "my-app-production",
      scheme: "my-app-production",
      androidBundleIdentifier: "com.my_app.app",
      iosBundleIdentifier: "com.my-app.app",
      icon: "./assets/images/my_app_production.png",
      splash_url: "./assets/images/splash.png",
      projectId: "", // You'll need to create your own project under your own Expo account
    };
  }

  if (buildEnvironment === "staging") {
    return {
      name: "my-app-staging",
      slug: "my-app-staging",
      scheme: "my-app-staging",
      androidBundleIdentifier: "com.staging.my_app.app",
      iosBundleIdentifier: "com.staging.my-app.app",
      icon: "./assets/images/my_app_staging.png",
      splash_url: "./assets/images/splash_staging.png",
      projectId: "", // You'll need to create your own project under your own Expo account
    };
  }

  return {
    name: "my-app-dev",
    slug: "my-app-dev",
    scheme: "my-app-dev",
    androidBundleIdentifier: "com.dev.my_app.app",
    iosBundleIdentifier: "com.dev.my-app.app",
    icon: "./assets/images/my_app_dev.png",
    splash_url: "./assets/images/splash_dev.png",
    projectId: "eb0d634d-67b0-4235-898d-861bcca807b7", // You'll need to create your own project under your own Expo account
  };
};

export default ({ config }: ConfigContext): ExpoConfig => {
  const appConfig = getAppConfig();

  return {
    owner: "my-org", // If running locally - change this to your Expo username
    ...config,
    ...appConfig,
    version: "1.0.0",
    orientation: "portrait",
    icon: appConfig.icon,
    userInterfaceStyle: "automatic",
    newArchEnabled: true,
    splash: {
      image: appConfig.splash_url,
      resizeMode: "contain",
      backgroundColor: "#d3b49a",
    },
    scheme: appConfig.scheme,
    assetBundlePatterns: ["**/*"],
    extra: {
      eas: {
        projectId: appConfig.projectId,
        apiUrl: process.env.API_URL,
        buildEnv: process.env.BUILD_ENV,
      },
    },
    ios: {
      bundleIdentifier: appConfig.iosBundleIdentifier,
      supportsTablet: false,
      icon: appConfig.icon,
      infoPlist: {
        ITSAppUsesNonExemptEncryption: false,
        NSBluetoothAlwaysUsageDescription:
          "This app uses Bluetooth to connect to supported card readers.",
        NSBluetoothPeripheralUsageDescription:
          "This app uses Bluetooth to connect to supported card readers.",
      },
    },
    android: {
      package: appConfig.androidBundleIdentifier,
      adaptiveIcon: {
        foregroundImage: appConfig.icon,
        backgroundColor: "#d3b49a",
      },
      permissions: [
        "BLUETOOTH",
        "BLUETOOTH_ADMIN",
        "NFC",
        "FOREGROUND_SERVICE",
      ],
      versionCode: 1,
      edgeToEdgeEnabled: true,
    },
    plugins: [
      "expo-router",
      "expo-secure-store",
      [
        "expo-splash-screen",
        {
          image: appConfig.splash_url,
          resizeMode: "cover",
          backgroundColor: "#d3b49a",
        },
      ],
      [
        "@stripe/stripe-terminal-react-native",
        {
          bluetoothBackgroundMode: true,
          locationWhenInUsePermission:
            "Location access is required in order to accept payments.",
          bluetoothPeripheralPermission:
            "Bluetooth access is required in order to connect to supported bluetooth card readers.",
          bluetoothAlwaysUsagePermission:
            "This app uses Bluetooth to connect to supported card readers.",
        },
      ],
    ],
    experiments: {
      typedRoutes: true,
    },
  };
};

package.json

{
  "scripts": {
    "start": "expo start",
    "dev": "BUILD_ENV=development expo start --dev-client",
    "ios": "BUILD_ENV=development expo run:ios",
    "android": "BUILD_ENV=development expo run:android",
    "prebuild": "expo prebuild",
    "build:dev": "BUILD_ENV=development eas build --profile development",
    "build:preview": "BUILD_ENV=preview eas build --profile preview",
    "build:staging": "BUILD_ENV=staging eas build --profile staging",
    "build:production": "BUILD_ENV=production eas build --profile production",
    "submit:staging": "BUILD_ENV=staging npx eas submit --profile staging",
    "submit:production": "BUILD_ENV=production npx eas submit --profile production",
    "test": "jest --watchAll",
    "lint": "expo lint"
  }
}