expo / eas-cli

Fastest way to build, submit, and update iOS and Android apps
https://docs.expo.dev/eas/
MIT License
823 stars 84 forks source link

Using environment variables in `app.config.ts` doesn't work with `eas update` #2222

Open andrejpavlovic opened 8 months ago

andrejpavlovic commented 8 months ago

Build/Submit details page URL

No response

Summary

How do I get eas update to properly load app.config.js file that references environment variables, if those aren't available when eas update is executed?

Let's say I have an .env file with this environment variable:

MY_VALUE=test

I use this value in app.config.js for something and ensure it is not empty:

export default ({ config })=> {
  if (!process.env.MY_VALUE) {
    throw new Error(`MY_VALUE environment variable not defined`)
  }

  // ... proceed to to use MY_VALUE in the config

  return config
}

✔️ Running expo command such as yarn expo config works fine, since the environment variable is automatically loaded from .env file.

❌ However when running eas config or eas update, the error above is thrown because MY_VALUE environment value is not available.

Now, I understand that eas is not supposed to be reading .env values, but the issue here is that it's reading the expo config which uses environment variables.

So I thought adding MY_VALUE to eas.json as env value would fix the issue, but it only works for eas config. eas update doesn't read environment values even from eas.json.

If eas update doesn't read environment values from .env or from eas.json then why is it loading app.config.js which more than likely relies on environment values in order to configure expo plugins, etc.?

My workaround for now was to just load .env manually in app.config.js:

import * as dotenv from 'dotenv'

// eas doesn't load values from `.env`, so make sure they are loaded here
dotenv.config({
  path: [path.resolve(__dirname, '.env.local'), path.resolve(__dirname, '.env')],
})

export default ({ config })=> {
  if (!process.env.MY_VALUE) {
    throw new Error(`MY_VALUE environment variable not defined`)
  }

  // ... proceed to to use MY_VALUE in the config

  return config
}

Managed or bare?

managed

Environment

expo-env-info 1.2.0 environment info: System: OS: Windows 10 10.0.19045 Binaries: Node: 18.18.0 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.19 - C:\Program Files (x86)\Yarn\bin\yarn.CMD npm: 10.2.5 - C:\Program Files\nodejs\npm.CMD Watchman: 20210110.135312.0 - C:\ProgramData\chocolatey\bin\watchman.EXE IDEs: Android Studio: AI-231.9392.1.2311.11330709 npmPackages: expo: ~50.0.6 => 50.0.6 react: 18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 react-native: 0.73.4 => 0.73.4 react-native-web: ~0.19.10 => 0.19.10 Expo Workflow: bare

✔ Check Expo config for common issues ✔ Check package.json for common issues ✔ Check dependencies for packages that should not be installed directly ✔ Check for common project setup issues ✔ Check npm/ yarn versions ✔ Check for issues with metro config ✔ Check Expo config (app.json/ app.config.js) schema ✔ Check that packages match versions required by installed Expo SDK ✔ Check that native modules do not use incompatible support packages ✔ Check for legacy global CLI installed locally ✔ Check that native modules use compatible support package versions for installed Expo SDK

Error output

No response

Reproducible demo or steps to reproduce from a blank project

Description posted above.

libcthorne commented 8 months ago

I just encountered the same issue. Thanks for the workaround for now!

doyoonkim12345 commented 8 months ago

did you create the prebuild? i encoutered same issue, i solved by prebuild creation instead of set NODE_ENV.

andrejpavlovic commented 8 months ago

did you create the prebuild? i encoutered same issue, i solved by prebuild creation instead of set NODE_ENV.

I don't think prebuild is relevant here, since prebuild is an expo command, and the question is really about how to access environment variables when using eas update

islamashraful commented 8 months ago

I was facing a similar issue, so hope I can share some insights.

I am using an expo app with different app variants [staging, production].

This is my eas config****

"staging": {
    "distribution": "store",
    "env": {
      "APP_VARIANT": "staging"
    },
    "channel": "staging",
    "autoIncrement": true
  },
  "production": {
    "channel": "production",
    "autoIncrement": true
  }

And my app.config.ts is something like this

export default ({ config }: ConfigContext): ExpoConfig => {
  const isStaging = process.env.APP_VARIANT === 'staging';

  const productionConfig: ExpoConfig = {
    ...config,
    name: 'App',
    ios: {
      bundleIdentifier: 'com.xxxx.influencer',
    },
    android: {
      package: 'com.xxxx.influencer',
    },
    extra: {
      eas: {
        projectId: '8c24d46b-xxxx-45f1-xxxx-xxx571c7xxxx',
      },
      GQL_URL: 'https://gql.xxxx.com/graphql/',
    },
    updates: {
      url: 'https://u.expo.dev/xxx-xxxx-45f1-9379-dce571cxxx',
    },
    runtimeVersion: {
      policy: 'appVersion',
    },
  };

  if (isStaging) {
    return {
      ...productionConfig,
      name: '(Stg)App',
      ios: {
        ...productionConfig.ios,
        bundleIdentifier: 'com.xxxx.influencer.staging',
      },
      android: {
        ...productionConfig.android,
        package: 'com.xxxx.influencer.staging',
      },
      extra: {
        ...productionConfig.extra,
        GQL_URL: 'https://stg-gql.xxxx.com/graphql/',
      },
    };
  }

  return productionConfig;
};

For building the app (for staging) I use these commands

eas build --profile staging --platform android
eas build --profile staging --platform ios

Till this point, everything works fine. The build can properly set all environment variables, as I have specified the profile with env on my eas.json.

Also, on the app.config.ts, it can pick up the correct env[staging].

The problem happens with the eas update!

I was trying to push an update on my staging variant with the following command

eas update --branch staging --message "Message..."

Although I was sending the update to the staging branch, the new update wasn't able to pick the APP_VARIANT correctly.

Probably, it was undefined on app.config.ts, and my staging app was behaving like the production app by using my production GQL_URL: 'https://gql.xxxx.com/graphql/', endpoint.

Finally, I was able to fix that by setting the APP_VARIANT with the eas update command like this

APP_VARIANT=staging eas update --branch staging --message "Message..."

On the CI, it will be something like this

APP_VARIANT=staging npx eas-cli update --branch staging --message="`git log -1 --pretty=%B`" --non-interactive
yonihod commented 8 months ago

I was facing a similar issue, so hope I can share some insights.

I am using an expo app with different app variants [staging, production].

This is my eas config

"staging": {
    "distribution": "store",
    "env": {
      "APP_VARIANT": "staging"
    },
    "channel": "staging",
    "autoIncrement": true
  },
  "production": {
    "channel": "production",
    "autoIncrement": true
  }

And my app.config.ts is something like this

export default ({ config }: ConfigContext): ExpoConfig => {
  const isStaging = process.env.APP_VARIANT === 'staging';

  const productionConfig: ExpoConfig = {
    ...config,
    name: 'App',
    ios: {
      bundleIdentifier: 'com.xxxx.influencer',
    },
    android: {
      package: 'com.xxxx.influencer',
    },
    extra: {
      eas: {
        projectId: '8c24d46b-xxxx-45f1-xxxx-xxx571c7xxxx',
      },
      GQL_URL: 'https://gql.xxxx.com/graphql/',
    },
    updates: {
      url: 'https://u.expo.dev/xxx-xxxx-45f1-9379-dce571cxxx',
    },
    runtimeVersion: {
      policy: 'appVersion',
    },
  };

  if (isStaging) {
    return {
      ...productionConfig,
      name: '(Stg)App',
      ios: {
        ...productionConfig.ios,
        bundleIdentifier: 'com.xxxx.influencer.staging',
      },
      android: {
        ...productionConfig.android,
        package: 'com.xxxx.influencer.staging',
      },
      extra: {
        ...productionConfig.extra,
        GQL_URL: 'https://stg-gql.xxxx.com/graphql/',
      },
    };
  }

  return productionConfig;
};

For building the app (for staging) I use these commands

eas build --profile staging --platform android
eas build --profile staging --platform ios

Till this point, everything works fine. The build can properly set all environment variables, as I have specified the profile with env on my eas.json.

Also, on the app.config.ts, it can pick up the correct env[staging].

The problem happens with the eas update!

I was trying to push an update on my staging variant with the following command

eas update --branch staging --message "Message..."

Although I was sending the update to the staging branch, the new update wasn't able to pick the APP_VARIANT correctly.

Probably, it was undefined on app.config.ts, and my staging app was behaving like the production app by using my production GQL_URL: 'https://gql.xxxx.com/graphql/', endpoint.

Finally, I was able to fix that by setting the APP_VARIANT with the eas update command like this

APP_VARIANT=staging eas update --branch staging --message "Message..."

On the CI, it will be something like this

APP_VARIANT=staging npx eas-cli update --branch staging --message="`git log -1 --pretty=%B`" --non-interactive

I am having an issue with the build not the eas update, I am trying to set a new projectId based on configurations but it keeps failing when running the eas build worker, i thought you might have an input here

ChromeQ commented 7 months ago

Thanks for your workaround @andrejpavlovic - however I found an issue which is preventing me from continuing, I load the dotenv config using the ES6 method: import 'dotenv/config'; as described here: https://github.com/motdotla/dotenv?tab=readme-ov-file#how-do-i-use-dotenv-with-import (I'm using dotenv-vault but not sure that is relevant as I can see the vars being loaded in logs)

And for some reason the config is being called 3 times, once with the correct process.env var set and twice more with it undefined:

// app.config.ts
export default ({ config }: ConfigContext): ExpoConfig => {
  const url = `https://u.expo.dev/${process.env.EXPO_PROJECT_ID}`;
  console.log('URL', url);
  ...
};
// output
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
URL https://u.expo.dev/undefined
URL https://u.expo.dev/undefined

It looks like you are using a dynamic configuration! Learn more: https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs)
Add the following EAS Update key-values to the project app.config.js:
Learn more: https://expo.fyi/eas-update-config

EAS is only being called once with this command: npx eas-cli@latest update --auto --non-interactive

Interestingly, when using your method of import * as dotenv from 'dotenv'; and calling dotenv.config() outside the scope of the config function (I don't require the config options with my dotenv-vault) then it seems to call it many more times but it does actually set the env var correctly every time:

// output
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[expo-cli] Starting Metro Bundler

It would be nice for this to work as expected by EAS.

ChromeQ commented 7 months ago

I have solved this issue for me anyway by utilising dotenvx which only injects the variables once and they persist no matter how many times eas calls the config function - and it keeps the config file cleaner.

This was discovered as dotenv-vault didn't play nicely with my .env.local file on development.

Summary: Don't change your app.config.js/ts - don't import dotenv Install dotenvx (and maybe remove dotenv) and use it to run eas:

npx dotenvx run -- npx eas-cli@latest update --auto --non-interactive

// Or simpler when using the (expo-gitub-action)[https://github.com/expo/expo-github-action/tree/v8/]
npx dotenvx run -- eas update --auto --non-interactive

If you need additional env vars passed in then set them before dotenvx:

APP_PROFILE=preview npx dotenvx run -- eas update --auto --non-interactive

// Not like this
npx dotenvx run -- APP_PROFILE=preview eas update --auto --non-interactive

Hope this helps someone until Expo/EAS fix this as it is a bit annoying and 🤯

AdamGerthel commented 7 months ago

Using dotenv cli to load the env variables is the only viable solution to this problem. The documentation is really confusing on this topic, especially since "update" is an EAS command, you'd expect it to use variables from eas.json at least, but no - it doesn't load variables from neither eas.json nor env files.

See https://github.com/expo/eas-cli/issues/1265#issuecomment-1499968948

Here's how I've done it (with dotenv-cli as a project dependency):

yarn dotenv -e .env -e .env.<environment> -- npx eas-cli@latest update --channel <channel>

ChromeQ commented 7 months ago

@AdamGerthel if you use npx then you don't need to install it as a project dependency, it will download it and use it on demand. eas.json is not very useful in my opinion as there are no dynamic values in json

DeepSwami commented 6 months ago

Thanks for your workaround @andrejpavlovic - however I found an issue which is preventing me from continuing, I load the dotenv config using the ES6 method: import 'dotenv/config'; as described here: https://github.com/motdotla/dotenv?tab=readme-ov-file#how-do-i-use-dotenv-with-import (I'm using dotenv-vault but not sure that is relevant as I can see the vars being loaded in logs)

And for some reason the config is being called 3 times, once with the correct process.env var set and twice more with it undefined:

// app.config.ts
export default ({ config }: ConfigContext): ExpoConfig => {
  const url = `https://u.expo.dev/${process.env.EXPO_PROJECT_ID}`;
  console.log('URL', url);
  ...
};
// output
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
URL https://u.expo.dev/undefined
URL https://u.expo.dev/undefined

It looks like you are using a dynamic configuration! Learn more: https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs)
Add the following EAS Update key-values to the project app.config.js:
Learn more: https://expo.fyi/eas-update-config

EAS is only being called once with this command: npx eas-cli@latest update --auto --non-interactive

Interestingly, when using your method of import * as dotenv from 'dotenv'; and calling dotenv.config() outside the scope of the config function (I don't require the config options with my dotenv-vault) then it seems to call it many more times but it does actually set the env var correctly every time:

// output
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
- Exporting...
[expo-cli] [dotenv@16.4.5][INFO] Loading env from encrypted .env.vault
- Exporting...
[expo-cli] URL https://u.expo.dev/022xxxxx-eb2e-xxx-b078-24xxxxxfb2d
[expo-cli] Starting Metro Bundler

It would be nice for this to work as expected by EAS.

import * as dotenv from 'dotenv'; dotenv.config();

The above approach solved my issue. the last call was not persisting env variables.

Thank you so much for this workaround😊

sprutner commented 4 months ago

@ChromeQ Thanks!!!! That helped me after a lot of frustration!

KirschX commented 4 months ago

npx dotenvx run -- eas update --auto --non-interactive

This is the easiest and cleanest solution for me. Thank you so much!

Guiles92 commented 2 months ago

I encountered the same issue. Using a TypeScript app.config file and an env file named .env.local, I got errors when trying to build or run npx expo-doctor, saying it couldn't find an env variable. The fix: add import * as dotenv from 'dotenv'; dotenv.config(); to the app.config.ts file. There's a catch though - either rename the file to just .env, or specify the file path in the config function like this: dotenv.config({ path: '.env.local' });.