All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.2.0] - 2026-08-19
Changed
BREAKING (behaviour):
CooperConfig.Provider.init/1andCooperConfig.load!/2now raiseArgumentErroron an option they do not recognize, instead of ignoring it.Neither this library nor Cooper rejected unknown keys, so a typo meant the option simply did not happen. That was found the hard way: passing
:secret_moduleto a version predating it produced a release whose secrets were revealed, with no warning anywhere. A provider that quietly does less than you asked is worse than one that refuses to boot.This also affects
:cacheand:watch_envonProvider, which were previously accepted and disregarded — the provider always loads withcache: false, becauseCooper.Cache's process has not started when a provider runs. Passing either now raises rather than silently doing the opposite of what was asked.The accepted list is enumerated in each module, which couples it to Cooper's own option set: an option added there needs adding here. That is deliberate — raising on a valid-but-newer option is loud and immediately diagnosable, while ignoring a security-relevant one is neither.
Added
:secret_module, accepted byCooperConfig.Convert.to_app_config/2,CooperConfig.load!/2andCooperConfig.Provider. EachCooper.Secretis revealed and immediately re-wrapped assecret_module.new(value).This is for a codebase that already owns a secret type and does not want a dependency's struct appearing in its own configuration contracts. It gets the same accidental-leak protection as
reveal_secrets: false, while consuming code reveals through a type it controls.It takes precedence over
:reveal_secrets, so a caller that names its own type cannot accidentally receive a plain string. It is a module rather than a function because these options are written into a release'ssys.config, where an atom round-trips dependably and a captured function does not.Defaults to
nil, which leaves existing behaviour exactly as it was.Docs are now also published to GitHub Pages on every push to
main(.github/workflows/docs.yml), as an unreleased-changes preview alongside the release docs on hexdocs.pm.
Changed
mix.exs'sdocs()now setscanonical: "https://hexdocs.pm/cooper_config", so the GitHub Pages copy doesn't compete with hexdocs.pm as duplicate content.
[0.1.1] - 2026-08-03
Changed
- Bumped the
cooperdependency to~> 0.2(from~> 0.2.0), picking upcooper0.2.2, which in turn requiresichor_runtime ~> 0.2(from~> 0.1.0).ichor_runtime0.2.0's breaking change (raw capture data is now an ordered[{name, value}]list instead of a plain map, fixing sibling-capture evaluation order) is internal tomix ichor.gen-generated parser code;cooper0.2.2 already regenerated its own grammar modules to match.cooper_confignever touches raw captures itself -- it only callsCooper.load_file/2's public API andIchor.Error.format/1-- so there's no observable behavior change here.
[0.1.0] - 2026-07-31
Added
CooperConfig.Provider, aConfig.Providerimplementation that loads a CASC file viaCooper.load_file/2at release boot and merges it into the release's app config, replacingconfig/runtime.exsfor the settings it covers.CooperConfig.Convert.to_app_config/2, converting aCooper-loaded value (string-keyed maps) into the[app: [key: value, ...]]shapeApplication/Config.Providerexpect, deep-converting nested maps into keyword lists soConfig.Reader.merge/2can merge them against existing config the same way multipleconfig.exsfiles merge.CooperConfig.Providernow accepts:dotenv/:dotenv_env/:dotenv_files, passed straight through toCooper.load_file/2(cooper0.2.0's new.envfile support).CooperConfig.load!/2, for loading a CASC file (config/config.cascby default) as the first line of your own application'sApplication.start/2-- the equivalent ofProviderformix run/iex -S mix/mix test, whereConfig.Providernever runs at all. UnlikeProvider, this loads withCooper.load_file/2's default cache, since:cooperhas already started by the time it's safe to call.
Changed
- Bumped the
cooperdependency to~> 0.2.0.CooperConfig.Providernow always loads withcache: false--cooper0.2.0 cachesCooper.load_file/2by default via aCooper.CacheGenServer/ETS table started bycooper's own OTP application, which isn't running yet whenConfig.Providercallbacks execute (they run before the release's own supervision tree starts, same reason a load failure raises instead of retrying -- see the moduledoc's "Errors" section); reaching for that cache here would crash, not just be pointless for a file read once per boot. - Note for anyone passing
:envexplicitly:cooper0.2.0 changed it from a full replacement ofSystem.get_env/0to an override layer on top of it (seecooper's own CHANGELOG for the full precedence chain) -- a${...}reference to a name not given an explicit:enventry now falls through to the real environment/.envfiles instead of being undefined.