Cooper. Dotenv
(Cooper v0.2.0)
Copy Markdown
Builds the map Cooper's ${...} resolution (CASC.md §7.2) reads
from. Called from Cooper.load_file/2/load_string/2 on every
single call, hit or miss, cached or not -- and again by
Cooper.Cache's own watch_env poll tick, so a .env file edit is
detected the same way a real System.put_env/2 is. Not a separate
pipeline stage callers normally reach for directly, but its own
module (rather than inlined) because the layering rules below are
non-trivial enough to want one place to read, and one place to test,
them.
Layering
Five layers, later winning, Dotenvy.source/2's own convention:
System.get_env/0-- always the floor, whether or not:envis passed..env.env.<env>--<env>, in order: the explicit:dotenv_envoption; else liveMix.env/0when Mix is loaded (true formix run/mix test/iex -S mix, false for a compiled OTP release); elseApplication.compile_env(:cooper, :dotenv_env)-- baked in at the host application's own compile time, the only way left to auto-detect an environment in a release. That last one only fires if the host app opts in withconfig :cooper, dotenv_env: config_env()in its ownconfig/config.exs-- deliberately not something Cooper can default on its own (seecompiled_env/0's own comment for why naively readingMix.env/0from inside Cooper's own source could never work here). No match at all leaves<env>(and this whole layer) absent..env.local- the
:envoption, if the caller passed one -- always the final, highest-precedence override, on top of every layer above it.
All of 2-4 are optional -- a missing file is silently skipped, never
a load-time error. .env.<env> in particular is expected to be
absent for every environment but the current one.
All paths are resolved relative to the current working directory,
deliberately not :root (load_file/2's config-file directory) --
.env files live at the project root regardless of where the CASC
file being loaded happens to sit.
:env is an override layer, not a replacement. Passing env: %{"FOO" => "bar"} does not isolate resolution from the real
environment or .env files -- it guarantees FOO resolves to
"bar" specifically, while every other ${...} reference still
falls through to .env/.env.local/the real OS environment. There
is currently no option that fully replaces every layer below it; a
test that needs a ${...} reference to resolve to a specific,
guaranteed-deterministic value should give that name an explicit
entry in :env rather than relying on it being otherwise unset.
Enabling
.env file loading (layers 2-4) is on by default; dotenv: false
disables just those three layers -- System.get_env/0 and an
explicit :env still apply either way. If :dotenvy isn't
installed: the default-enabled case silently no-ops (same as if no
.env files existed), but an explicit dotenv: true with the
dependency missing is a load-time error naming it -- asking for it by
name and not getting it is a real misconfiguration, not something to
paper over.
Summary
Functions
Resolves the final env map for opts (the same options load_file/2/
load_string/2 take): System.get_env/0, .env-layered per the
moduledoc above unless disabled, with :env (if given) applied last
as the final override.
Types
Functions
@spec env(opts()) :: {:ok, %{required(String.t()) => String.t()}} | {:error, Ichor.Error.t()}
Resolves the final env map for opts (the same options load_file/2/
load_string/2 take): System.get_env/0, .env-layered per the
moduledoc above unless disabled, with :env (if given) applied last
as the final override.