294 lines · 10.1 KB · TypeScript
RawDownload
import type { ExpoConfig } from "expo/config"; import { loadRepoEnv } from "../../scripts/lib/public-config.ts"; type AppVariant = "development" | "preview" | "production"; const repoEnv = loadRepoEnv();Object.assign(process.env, repoEnv); const APP_VARIANT = resolveAppVariant(repoEnv.APP_VARIANT);const isIosPersonalTeamBuild = repoEnv.T3CODE_IOS_PERSONAL_TEAM === "1"; const personalTeamBundleIdentifier = repoEnv.T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID?.trim();const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/; if (  isIosPersonalTeamBuild &&  (!personalTeamBundleIdentifier ||    !IOS_BUNDLE_IDENTIFIER_PATTERN.test(personalTeamBundleIdentifier))) {  throw new Error(    "T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID must be a reverse-DNS identifier such as com.example.t3code when T3CODE_IOS_PERSONAL_TEAM=1.",  );} const VARIANT_CONFIG: Record<  AppVariant,  {    readonly appName: string;    readonly scheme: string;    readonly iosIcon: string;    readonly splashIcon: string;    readonly iosBundleIdentifier: string;    readonly androidPackage: string;    readonly relyingParty?: string;  }> = {  development: {    appName: "T3 Code Dev",    scheme: "t3code-dev",    iosIcon: "./assets/icon-composer-dev.icon",    splashIcon: "./assets/splash-icon-dev.png",    iosBundleIdentifier: "com.t3tools.t3code.dev",    androidPackage: "com.t3tools.t3code.dev",    relyingParty: "clerk.t3.codes",  },  preview: {    appName: "T3 Code Preview",    scheme: "t3code-preview",    iosIcon: "./assets/icon-composer-prod.icon",    splashIcon: "./assets/splash-icon-prod.png",    iosBundleIdentifier: "com.t3tools.t3code.preview",    androidPackage: "com.t3tools.t3code.preview",    relyingParty: "clerk.t3.codes",  },  production: {    appName: "T3 Code",    scheme: "t3code",    iosIcon: "./assets/icon-composer-prod.icon",    splashIcon: "./assets/splash-icon-prod.png",    iosBundleIdentifier: "com.t3tools.t3code",    androidPackage: "com.t3tools.t3code",    relyingParty: "clerk.t3.codes",  },}; function resolveAppVariant(value: string | undefined): AppVariant {  switch (value) {    case "development":    case "preview":    case "production":      return value;    default:      return "production";  }} const variant = VARIANT_CONFIG[APP_VARIANT]; const dmSansFonts = {  regular: "@expo-google-fonts/dm-sans/400Regular/DMSans_400Regular.ttf",  medium: "@expo-google-fonts/dm-sans/500Medium/DMSans_500Medium.ttf",  bold: "@expo-google-fonts/dm-sans/700Bold/DMSans_700Bold.ttf",} as const; const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [  "expo-widgets",  {    bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,    groupIdentifier: `group.${variant.iosBundleIdentifier}`,    enablePushNotifications: true,    // Agent activity can update many times an hour; without the    // frequent-updates entitlement iOS throttles the update budget sooner.    frequentUpdates: true,    widgets: [      {        name: "AgentActivity",        displayName: "Agent Activity",        description: "Shows the current state of active T3 Code agents.",        supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],      },    ],  },]; // These aliases match the fonts' PostScript names on iOS. Register the same// names on Android so React Native and the native composer use one set of// family names without waiting for runtime font loading. const config: ExpoConfig = {  name: variant.appName,  slug: "t3-code",  platforms: ["ios", "android"],  scheme: variant.scheme,  version: "0.1.0",  runtimeVersion: {    // Fingerprint (not appVersion) so an OTA only reaches binaries whose native    // project — native deps, config plugins, AND patches/ — matches the update.    // With appVersion, every 0.1.0 build shares a runtime version, so a JS update    // could land on a binary missing the native changes it needs and crash.    policy: process.env.MOBILE_VERSION_POLICY ?? "fingerprint",  },  orientation: "portrait",  icon: "./assets/icon.png",  userInterfaceStyle: "automatic",  updates: {    enabled: true,    url: "https://u.expo.dev/d763fcb8-d37c-41ea-a773-b54a0ab4a454",    checkAutomatically: "ON_LOAD",    fallbackToCacheTimeout: 0,  },  ios: {    icon: variant.iosIcon,    supportsTablet: true,    bundleIdentifier: variant.iosBundleIdentifier,    // Pin code signing to the T3 Tools team so non-interactive `expo run:ios`    // does not fall back to a personal team (which cannot sign app groups,    // Sign in with Apple, or push notification entitlements).    appleTeamId: "ARK85ZXQ4Z",    associatedDomains: [      `applinks:${variant.relyingParty}`,      `webcredentials:${variant.relyingParty}`,    ],    infoPlist: {      NSAppTransportSecurity: {        NSAllowsArbitraryLoads: true,      },      NSLocalNetworkUsageDescription:        "Allow T3 Code to connect to T3 Code servers on your local network or tailnet.",      ITSAppUsesNonExemptEncryption: false,    },  },  android: {    icon: "./assets/icon.png",    package: variant.androidPackage,    adaptiveIcon: {      backgroundColor: "#E6F4FE",      foregroundImage: "./assets/android-icon-foreground.png",      backgroundImage: "./assets/android-icon-background.png",      monochromeImage: "./assets/android-icon-monochrome.png",    },    // Opts into OnBackInvokedCallback-based back dispatch (Android 13+).    // JS back handling survives it via react-native's Android 16 shim plus    // withAndroidPredictiveBackCompat on Android 13-15.    predictiveBackGestureEnabled: true,  },  web: {    favicon: "./assets/favicon.png",  },  plugins: [    "expo-asset",    [      "expo-font",      {        ios: {          fonts: [dmSansFonts.regular, dmSansFonts.medium, dmSansFonts.bold],        },        android: {          fonts: [            {              fontFamily: "DMSans-Regular",              fontDefinitions: [{ path: dmSansFonts.regular, weight: 400 }],            },            {              fontFamily: "DMSans-Medium",              fontDefinitions: [{ path: dmSansFonts.medium, weight: 500 }],            },            {              fontFamily: "DMSans-Bold",              fontDefinitions: [{ path: dmSansFonts.bold, weight: 700 }],            },          ],        },      },    ],    "expo-secure-store",    "expo-sqlite",    // appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before    // plugins earlier in this array, so it cannot strip the entitlement Clerk would add.    ["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }],    "expo-web-browser",    [      "expo-quick-actions",      {        // Adaptive launcher-shortcut icon; referenced by resource name from        // the shortcut items set in src/features/shortcuts.        androidIcons: {          shortcut_icon: {            foregroundImage: "./assets/android-icon-foreground.png",            backgroundColor: "#E6F4FE",          },        },      },    ],    [      "expo-camera",      {        cameraPermission: "Allow T3 Code to access your camera so you can scan pairing QR codes.",        barcodeScannerEnabled: true,      },    ],    [      "expo-splash-screen",      {        image: variant.splashIcon,        resizeMode: "contain",        backgroundColor: "#ffffff",        imageWidth: 220,        dark: {          image: variant.splashIcon,          backgroundColor: "#0a0a0a",        },      },    ],    [      "expo-build-properties",      {        ios: {          deploymentTarget: "18.0",          // AppCheckCore 11.3+ includes Swift and needs module maps for these Objective-C dependencies.          extraPods: [            { name: "GoogleUtilities", modular_headers: true },            { name: "RecaptchaInterop", modular_headers: true },          ],        },      },    ],    "./plugins/withIosCocoaPodsUuidCache.cjs",    // Must be listed BEFORE expo-widgets: same-type mods run last-registered-    // first, so registering earlier makes this plugin's mods run AFTER    // expo-widgets' — its dangerous mod wipes ios/ExpoWidgetsTarget/ (which    // would delete the asset catalog) and its xcodeproj mod creates the widget    // target (which must exist before the compile phase can be attached).    ...(!isIosPersonalTeamBuild ? ["./plugins/withWidgetLogoAsset.cjs", widgetsPlugin] : []),    "./plugins/withIosSceneLifecycle.cjs",    "./plugins/withAndroidCleartextTraffic.cjs",    "./plugins/withAndroidGradleHeap.cjs",    "./plugins/withAndroidModernPopupMenu.cjs",    "./plugins/withAndroidModernAlertDialog.cjs",    "./plugins/withAndroidPredictiveBackCompat.cjs",    ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []),  ],  extra: {    appVariant: APP_VARIANT,    iosPersonalTeamBuild: isIosPersonalTeamBuild,    relay: {      url: repoEnv.T3CODE_RELAY_URL ?? null,    },    clerk: {      publishableKey: repoEnv.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY ?? null,      jwtTemplate: repoEnv.EXPO_PUBLIC_CLERK_JWT_TEMPLATE ?? null,    },    // Native Google sign-in credentials. @clerk/expo reads these from `extra`    // under their exact env-var names (not nested), and its config plugin reads    // the iOS URL scheme at prebuild to register it in Info.plist.    // Unset values must be omitted (not null): the public manifest serializes    // null to {}, which is truthy and would defeat Clerk's fallback checks.    EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID,    EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID,    EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID,    EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME,    observability: {      tracesUrl: repoEnv.EXPO_PUBLIC_OTLP_TRACES_URL ?? "https://api.axiom.co/v1/traces",      tracesDataset: repoEnv.EXPO_PUBLIC_OTLP_TRACES_DATASET ?? null,      tracesToken: repoEnv.EXPO_PUBLIC_OTLP_TRACES_TOKEN ?? null,    },    eas: {      projectId: "d763fcb8-d37c-41ea-a773-b54a0ab4a454",    },  },  owner: "pingdotgg",}; export default config;