Yeah embedding the config in the application code is definitely a trade-off! Limiting it to dev/prd in the code has the benefits that it is very explicit what the expected production behaviour is for all developers working on main, all the way through to the InteliSense in their IDE.
On the other hand, it reduces flexibility in terms of deploying ephemeral environments. However, I find that use case comes up much less often -- the idea of deploying a new environment for every open pull request for example, doesn't seem advantageous. Doing so would slow down pull request cycle time, which naturally leads developers to make bigger pull requests, which further slows the time to close each PR while also increasing the risk of change fail.
The case where ephemeral environments have the most benefit is in planning for large migrations, where teams may need extensive exploratory testing and/or rehearsals of runsheets. In those instances, adding additional config is comparatively trivial.
Hah, quite similar to what we did at FMG. We follow 12factor app principles, those config are retrieved from env flags. We have some layers for generating the env vars, from ansible vars files (which is also in source control) and from the CI server for credentials. Each environment (dev, stg, prod) has its own Ansible file, where you can define which feature you want to turn on and monitor via source control. We store those config in source control but not in js/ts code. Your code tight to only those 3 env. Putting it in Ansible allows us to decouple the env with the logic code. It also helps us spin up a new environment easily, test env for example, or maybe multiple stg environments if you have multiple teams.