PR: Make Varlock authoritative under Bun by neutralizing Bun dotenv and hardening resolver #141
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
--env-file, while preserving runtime-agnostic semantics and keeping UX simple.@envFlagvalue/name, is zero-config for most users, and remains portable to other runtimes.References:
Why this bug happens under Bun
Bun auto-loads dotenv files based on
NODE_ENV(in precedence order):.env→.env.(development|production|test)→.env.local. It does not recognize custom names (e.g.,.env.staging). See Bun environment variables.Varlock’s model resolves from
.env.schema+.env.<@envFlag>(e.g.,APP_ENV) regardless ofNODE_ENV. When Bun autoloads before or alongside Varlock, values from.env.developmentand/or.env.localcan override the intended values, especially whenNODE_ENVis defaulting to development. This is the core of #139.Why a Varlock-core fix was proposed instead of a Bun plugin
--env-filedeterministically controls dotenv behavior; passing an empty file results in no autoload. This is stable and documented in Bun environment variables.varlock load/varlock run. No per-project preloads or bunfig changes.What changed (code and behavior)
Harden resolver to ignore ambient
process.envfor schema-defined keys by defaultpackages/varlock/env-graph/lib/config-item.tsprocess.envoverrides viaEnvGraph.respectExistingEnv:process.envwill not override it unlessrespectExistingEnvis true.@envFlagkey (e.g.,APP_ENV) to be set fromprocess.envduring the early pass so Varlock can select the correct.env.<env>files from the start.process.env.Default handling for
.env.localis opt-outpackages/varlock/env-graph/lib/loader.ts.env.localand.env.<env>.localby default (consistent with existing Varlock behavior).excludeLocal?: booleanto disable locals when explicitly requested.packages/varlock/src/lib/load-graph.tsplumbsexcludeLocalandrespectExistingEnv.packages/varlock/src/cli/commands/load.command.tsadds flags:--exclude-local: Exclude.env.localand.env.[env].local--respect-existing-env: Allow ambientprocess.envto override schema-defined keyspackages/varlock/src/cli/commands/run.command.tsmirrors the same flags for the run path.Bun-aware runner
packages/varlock/src/cli/commands/run.command.tsbun/bunxand neutralizes Bun dotenv by passing an empty--env-file.PATH,HOME,SHELL,TERM,TZ,LANG,LC_ALL,PWD,TMPDIR,TEMP,TMP) + all Varlock-resolved keys.--bun-sync-node-env: setsNODE_ENVto the resolved@envFlagvalue when enabled. Off by default to avoid surprises..env.localdefaults apply to all commands (Node or Bun).bunorbunx.Backward compatibility and UX
import { ENV } from 'varlock/env'still worksvarlock/auto-load) are unchanged.ENVis still initialized fromprocess.env.__VARLOCK_ENV, injected byvarlock runor integrations, and exposes resolved values to application code.Default behavior changes you should be aware of
process.envvalues no longer override schema-defined keys. Restore old behavior with--respect-existing-env(or a future.varlockrcdefault)..env.local: Included by default, consistent with the current Varlock mental model. You can explicitly opt-out via--exclude-local.varlock rundisables Bun dotenv to ensure no drift from Varlock’s resolved env.Performance
How it works (step-by-step)
.env.*files in the project directory, parses.env.schema, discovers@envFlag(e.g.,APP_ENV).APP_ENVis defined in the ambient environment, it is respected for this key only to select the appropriate.env.<env>files..env.schema, the environment-specific file(s), and optionally.env.localper flag. Ambientprocess.envis ignored for these keys by default (unless--respect-existing-envis set).varlock run -- bun ...orvarlock run -- bunx ...:--env-file <empty-temp-file>, neutralizing Bun’s dotenv mechanism.NODE_ENVto the resolved@envFlagvalue if--bun-sync-node-envis provided.New Flags and defaults
--exclude-local: Exclude.env.localand.env.[env].local. Default: not excluded (included).--respect-existing-env: Allow ambientprocess.envto override schema-defined keys. Default: false.--bun-sync-node-env: When running Bun, setNODE_ENVto the resolved@envFlagvalue. Default: false.Code references (selected)
Resolver hardening and env flag special-case
packages/varlock/env-graph/lib/config-item.ts:process.envoverrides only whenrespectExistingEnvis true or the item is not in the schema.process.envto set the env flag key (e.g.,APP_ENV) during the early pass so.env.<env>selection works..env file loading, opt-out
.env.localpackages/varlock/env-graph/lib/loader.ts:.env.*files.excludeLocal === true.CLI flags
packages/varlock/src/cli/commands/load.command.ts:--exclude-local,--respect-existing-env,--env.packages/varlock/src/cli/commands/run.command.ts:--exclude-local,--respect-existing-env,--bun-sync-node-env,--env.Bun-aware runner
packages/varlock/src/cli/commands/run.command.ts:bun/bunx.--env-file <empty-temp-file>.NODE_ENVsync from@envFlag.Testing instructions
A ready-to-run Bun-based DevContainer test project (with updated executables) is available for reviewers:
varlock-testing DevContainer
It contains:
.env*files (.env.schema,.env.production,.env.development,.env.local, etc.)script.tsthat logs selected variables usingprocess.env(do not importvarlock/envfor this specific test path).Uses an SEA bundle of the modified Varlock CLI (no install required in the test project):
Basic resolution (JSON):
Run a Bun script with dotenv neutralized:
Select staging env (env flag via ambient):
Opt-out
.env.local:Allow ambient process.env to override schema-defined keys (off by default):
OVERRIDE=from-ambient APP_ENV=production bun ./varlock-cli-executable-bun-fix.cjs load --respect-existing-env --format json | jq .OVERRIDESync NODE_ENV to your
@envFlagvalue (optional):Expected behavior:
APP_ENV=production, values come from.env.schema+.env.production(+.env.localunless excluded).APP_ENV=staging, values come from.env.schema+.env.staging(+.env.localunless excluded).OVERRIDEonly comes from ambient when--respect-existing-envis used.varlock run.Maintainability and portability
--respect-existing-env,--exclude-local,--bun-sync-node-env.Compatibility notes and migration guidance
--respect-existing-envto your CLI invocations (or adopt a config default when available)..env.localremains included by default for local dev parity. Use--exclude-localif you want to disable it for specific runs.Acknowledgements and prior art
Proposed .varlockrc (optional, future docs)
{ "resolver": { "respectExistingEnv": false }, "bun": { "dotenv": "none", "syncNodeEnv": false } }This example captures the defaults introduced in this PR and shows how a future
.varlockrccould be used to centralize project-wide preferences. We can add this to the docs as a forward-looking example.