That’s why my personal preference is to keep the config file as code but to keep it as close to a static config as possible. The Turing completeness of the environment is there merely as an escape hatch, for the one branch you need to make.
If your tooling requires static configs, but you really need dynamism, you’re stuck with dynamically generated static configs, which are even worse than a dynamic config.
As I've mentioned elsewhere, I agree, but I think in an ideal world, there would be an explicit syntactic construct warning everyone where the program was using the escape hatch to leave the Turing-incomplete subset.
You could create and use an embedded DSL tailored specifically for your application. This is what Jenkins does with their "declarative pipeline": it's basically Groovy-based DSL, and if you need anything not covered by it you need to use `script { ... }` element.
The problem here is of course how not to make your DSL suck, which Jenkins' did not avoid. But if you're lucky, your language of choice already has a few libraries implementing embedded DSLs for configuration, and it's quite possible that at least one of them is actually usable.
If your tooling requires static configs, but you really need dynamism, you’re stuck with dynamically generated static configs, which are even worse than a dynamic config.