java-james / flutter_dotenv

Loads environment variables from `.env`.
https://pub.dartlang.org/packages/flutter_dotenv
MIT License
220 stars 47 forks source link

Accept loading multiple files with precedence #90

Open lucasoares opened 1 year ago

lucasoares commented 1 year ago

Hello.

I would like this library to accept multiple files.

Use case scenario: to have both .env.local and .env files, where my .env can be committed to the source control (usually constants, etc) but with a .env.local where I can have these constants changed only locally and ignored by git. This will let developers change the environment without changing the main file.

BernardinD commented 1 year ago

I also wanted to know if this is possible, if not already implemented.

LassazVegaz commented 1 year ago

I upvote this feature request. In total, I expect the following files,

.env                # loaded in all cases
.env.local          # loaded in all cases, usually ignored by git
.env.[mode]         # only loaded in specified mode
.env.[mode].local   # only loaded in the specified mode, usually ignored by git

mode is production or development. An env file for a specific mode (e.g. .env.production) will take higher priority than a generic one (e.g. .env)

This behavior is beautifully explained in the Vite build tool and I copied most of this from there.

Vite also includes test as one of the modes but I am not sure if the dart code can detect if the current running mode is test. Anyway production and development and their locals would be enough for now.

For this feature implementation, most of the existing functions can be used. The only extra code needed is to check if these files exist and merge them in the relevant order depending on the current mode. By checking if the file exists, I mean checking if the files are included in the project through pubspec.yaml.

lucasoares commented 1 year ago

I upvote this feature request. In total, I expect the following files,

.env                # loaded in all cases
.env.local          # loaded in all cases, usually ignored by git
.env.[mode]         # only loaded in specified mode
.env.[mode].local   # only loaded in the specified mode, usually ignored by git

mode is production or development. An env file for a specific mode (e.g. .env.production) will take higher priority than a generic one (e.g. .env)

This behavior is beautifully explained in the Vite build tool and I copied most of this from there.

Vite also includes test as one of the modes but I am not sure if the dart code can detect if the current running mode is test. Anyway production and development and their locals would be enough for now.

For this feature implementation, most of the existing functions can be used. The only extra code needed is to check if these files exist and merge them in the relevant order depending on the current mode. By checking if the file exists, I mean checking if the files are included in the project through pubspec.yaml.

Why not let users manually set the files they want to load in the order they want to load it? Simple and effective.

LassazVegaz commented 1 year ago

Why not let users manually set the files they want to load in the order they want to load it? Simple and effective.

But this is the usual way. I am saying, merge these files only if they exist. Most of the times these are the requirements. If those files don't exist, they won't be merged. Assume, a developer has most of these files. It won't be simple for that developer to merge those .ENVs by checking the current mode. That is why most of the build tools (Vite, CRA, Nest etc.) support this behavior out of the box. The explained feature wouldn't break any existing code and it will make development much easier when there is a lot of configuration. Anyway, I am planning to fork and develop the feature I mentioned.

pimvanderheijden commented 8 months ago

I agree with @lucasoares that this is standard. One question though, how and where does .env.production or production.env get created? And how do these variables (and thus not the .env file) get baked into the code that gets shipped to the user's device?

LassazVegaz commented 8 months ago

One question though, how and where does .env.production or production.env get created?

created by the developer

how do these variables (and thus not the .env file) get baked into the code that gets shipped to the user's device?

That depends on the platform you are building for. Flutter decides how the files mentioned in pubspec.yml are embedded into the application.

pimvanderheijden commented 8 months ago

created by the developer

Not necessarily, think CI/CD

That depends....

Think CI/CD

In other words, the production.env or .env.production file is not checked in. It may be created during some kind of CI/CD job in Gitlab in Github or whatever. However, on these CI/CD platforms it is common to inject env vars directly, for example from the repository settings. In that case, there is no production file...

And thus I'm starting to wonder if one ever would create / generate a production env file...

LassazVegaz commented 8 months ago

And thus I'm starting to wonder if one ever would create / generate a production env file...

When doing local development, the developer has to. Plus, can you confirm if the Flutter apps built process can be automated?

pimvanderheijden commented 8 months ago

Yes it can.

https://docs.flutter.dev/deployment/cd

LassazVegaz commented 8 months ago

@pimvanderheijden Oh wow didn't see that!

However, on these CI/CD platforms it is common to inject env vars directly, for example from the repository settings. In that case, there is no production file...

And thus I'm starting to wonder if one ever would create / generate a production env file...

You said, "it is common to". So you know in some cases, developers put the .env file instead of the commands because it is easy to manage env vars that way. When env vars change, just replace the file instead of changing commands. To do that the developer needs to keep a production level .env somewhere.

pimvanderheijden commented 8 months ago

Exactly, but where do you keep that file (surely not in Git) and decide to activate it yes/no? That’s an important question I think.

Additionally, assuming it is common to inject env vars from the platform (as opposed to only from an .env file), you need some mechanism that allows them to have precedence. For example, ‘API_KEY’ can be defined in both the development .env ánd in the CI/CD platform. Both get injected independently, so how to create precedence?

I think this repository deals with these challenges, but I don’t know how: https://www.npmjs.com/package/dotenv

On Mon, 25 Mar 2024 at 05:42, Lasindu Weerasinghe @.***> wrote:

@pimvanderheijden https://github.com/pimvanderheijden Oh wow didn't see that!

However, on these CI/CD platforms it is common to inject env vars directly, for example from the repository settings. In that case, there is no production file...

And thus I'm starting to wonder if one ever would create / generate a production env file...

You said, "it is common to". So you know in some cases, developers put the .env file instead of the commands because it is easy to manage env vars that way. When env vars change, just replace the file instead of changing commands. To do that the developer needs to keep a production level .env somewhere.

— Reply to this email directly, view it on GitHub https://github.com/java-james/flutter_dotenv/issues/90#issuecomment-2017199241, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFL2SSUPY5BPBZY5SPW57HLYZ6TMZAVCNFSM6AAAAAA2ZJCV52VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMJXGE4TSMRUGE . You are receiving this because you were mentioned.Message ID: @.***>

LassazVegaz commented 8 months ago

Exactly, but where do you keep that file (surely not in Git) and decide to activate it yes/no?

It can be kept locally in the repository. Even the production .env files need to be Git ignored.

so how to create precedence?

In Flutter, you can check if the current environment is development or production. According to that, the library can give priority to the specific .env file types.

I think this repository deals with these challenges, but I don’t know how:

The package you mentioned does that. It is the goto package for .envs in NodeJS. And this Flutter package can do the same. It replaces the values of existing keys from the next .env file. The source code is very simple, have a look.

pimvanderheijden commented 8 months ago

It can be kept locally in the repository. Even the production .env files need to be Git ignored.

I get that you can use production .env files when deploying from a local machine. However, the consequence of CI/CD is that local files (not checked in with Git) are irrelevant. I really don't know yet if I'm going to do CI/CD for my app anytime soon so won't really be able to tell you more. I do expect it's going to be a lot different than "just" an .env file.

In Flutter, you can check if the current environment is development or production.

So, load production .env file depending on kDebugMode and/or kReleaseMode booleans? Sounds great to me 😃

LassazVegaz commented 8 months ago

Good luck with that. But I still don't understand how a pipeline can build and publish Android and iOS apps to relevant stores. Sounds very impossible with all the restrictions they have. Specially Apple ones.

So, load production .env file depending on kDebugMode and/or kReleaseMode booleans?

Oh yeah!

pimvanderheijden commented 7 months ago

There is CI/CD for iOS with the help of fastlane: https://docs.fastlane.tools I don't know how it works for production releases but I'm sure it will be relevant at some point.

LassazVegaz commented 7 months ago

Looks like they have spent a lot of money to get those permissions

pimvanderheijden commented 7 months ago

And hence the user of flutter_dotenv will have to spend a lot of money too?

LassazVegaz commented 7 months ago

If you use this service to build a CD pipeline, disregardless of using this library, you will have to spend money.

pimvanderheijden commented 7 months ago

Hahah yes, clear. But I get a sense you're suggesting it's not worth the effort for fluttter_dotenv to create support for CI/CD 😄

LassazVegaz commented 7 months ago

No! 😕

pimvanderheijden commented 7 months ago

I found another part of the docs describing CI/CD for Flutter: https://docs.flutter.dev/deployment/ios#create-a-build-archive-with-codemagic-cli-tools

Doesn't sound like it's paid...

lucasoares commented 7 months ago

Guys, what CI/CD have to do with having precedent .env files, lol hahaha

.env (except .env.local) files are always present on git, there is no "CI/CD generating it". It should be used to toggle between public variables in the resulting build.

But it can be simplified if this library lets users load multiple .env files as they want:

await dotenv.load(".env", ".env.prod", ".env.local");

How the .env file will be shipped with the application, the priority of the loading, and anything go to the dev to decide.

What I need and what is the main point of this issue are documented here: https://vitejs.dev/guide/env-and-mode.html#env-files

Anything else doesn't belong to this issue. Discuss it elsewhere, not in here! Thanks!

LassazVegaz commented 7 months ago

@pimvanderheijden sounds cool.

@lucasoares

Guys, what CI/CD have to do with having precedent .env files, lol hahaha

Adding production .envs while building

.env (except .env.local) files are always present on git

Not always. Only in projects where inexperienced developers make decisions.

roman910dev commented 4 months ago
Future<void> loadEnvs(List<String> envs, {Map<String, String> merge = const {}}) async {
  if (envs.isEmpty) return;
  if (envs.length == 1) return dotenv.load(fileName: envs[0], mergeWith: merge);
  final de = DotEnv();
  await de.load(fileName: envs[0], mergeWith: merge);
  await _loadEnvs(envs.skip(1).toList(), merge: de.env);
}

This loads any number of dotenv files serially. The firstmost have priority, later dotenvs do not override them. After the function ends, all are loaded in the dotenv variable.

Ahmed-gubara commented 3 weeks ago
Future<void> loadEnv() async {
  const isProduction = 'production' == String.fromEnvironment('ENVIRONMENT');

  // priority: environment > .env
  if (isProduction) {
    await dotenv.load(fileName: '.env', mergeWith: Platform.environment);
  }

  // priority: environment > .env.local > .env
  await dotenv.load(fileName: '.env.local', mergeWith: Platform.environment);
  await dotenv.load(fileName: '.env', mergeWith: {...dotenv.env});
}

In production builds, .env.local is ignored. Just pass it as a dart define, for example: flutter build apk --dart-define=ENVIRONMENT=production


{...dotenv.env} must be used to copy the map; because dotenv.load() will clear the its internal .env map, and values will be lost of not copied.