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.@envFlag
value/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.development
and/or.env.local
can override the intended values, especially whenNODE_ENV
is defaulting to development. This is the core of #139.Why a Varlock-core fix was proposed instead of a Bun plugin
--env-file
deterministically 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.env
for schema-defined keys by defaultpackages/varlock/env-graph/lib/config-item.ts
process.env
overrides viaEnvGraph.respectExistingEnv
:process.env
will not override it unlessrespectExistingEnv
is true.@envFlag
key (e.g.,APP_ENV
) to be set fromprocess.env
during the early pass so Varlock can select the correct.env.<env>
files from the start.process.env
.Default handling for
.env.local
is opt-outpackages/varlock/env-graph/lib/loader.ts
.env.local
and.env.<env>.local
by default (consistent with existing Varlock behavior).excludeLocal?: boolean
to disable locals when explicitly requested.packages/varlock/src/lib/load-graph.ts
plumbsexcludeLocal
andrespectExistingEnv
.packages/varlock/src/cli/commands/load.command.ts
adds flags:--exclude-local
: Exclude.env.local
and.env.[env].local
--respect-existing-env
: Allow ambientprocess.env
to override schema-defined keyspackages/varlock/src/cli/commands/run.command.ts
mirrors the same flags for the run path.Bun-aware runner
packages/varlock/src/cli/commands/run.command.ts
bun
/bunx
and 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_ENV
to the resolved@envFlag
value when enabled. Off by default to avoid surprises..env.local
defaults apply to all commands (Node or Bun).bun
orbunx
.Backward compatibility and UX
import { ENV } from 'varlock/env'
still worksvarlock/auto-load
) are unchanged.ENV
is still initialized fromprocess.env.__VARLOCK_ENV
, injected byvarlock run
or integrations, and exposes resolved values to application code.Default behavior changes you should be aware of
process.env
values no longer override schema-defined keys. Restore old behavior with--respect-existing-env
(or a future.varlockrc
default)..env.local
: Included by default, consistent with the current Varlock mental model. You can explicitly opt-out via--exclude-local
.varlock run
disables 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_ENV
is 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.local
per flag. Ambientprocess.env
is ignored for these keys by default (unless--respect-existing-env
is set).varlock run -- bun ...
orvarlock run -- bunx ...
:--env-file <empty-temp-file>
, neutralizing Bun’s dotenv mechanism.NODE_ENV
to the resolved@envFlag
value if--bun-sync-node-env
is provided.New Flags and defaults
--exclude-local
: Exclude.env.local
and.env.[env].local
. Default: not excluded (included).--respect-existing-env
: Allow ambientprocess.env
to override schema-defined keys. Default: false.--bun-sync-node-env
: When running Bun, setNODE_ENV
to the resolved@envFlag
value. Default: false.Code references (selected)
Resolver hardening and env flag special-case
packages/varlock/env-graph/lib/config-item.ts
:process.env
overrides only whenrespectExistingEnv
is true or the item is not in the schema.process.env
to set the env flag key (e.g.,APP_ENV
) during the early pass so.env.<env>
selection works..env file loading, opt-out
.env.local
packages/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_ENV
sync 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.ts
that logs selected variables usingprocess.env
(do not importvarlock/env
for 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 .OVERRIDE
Sync NODE_ENV to your
@envFlag
value (optional):Expected behavior:
APP_ENV=production
, values come from.env.schema
+.env.production
(+.env.local
unless excluded).APP_ENV=staging
, values come from.env.schema
+.env.staging
(+.env.local
unless excluded).OVERRIDE
only comes from ambient when--respect-existing-env
is used.varlock run
.Maintainability and portability
--respect-existing-env
,--exclude-local
,--bun-sync-node-env
.Compatibility notes and migration guidance
--respect-existing-env
to your CLI invocations (or adopt a config default when available)..env.local
remains included by default for local dev parity. Use--exclude-local
if you want to disable it for specific runs.Acknowledgements and prior art
Proposed .varlockrc (optional, future docs)
This example captures the defaults introduced in this PR and shows how a future
.varlockrc
could be used to centralize project-wide preferences. We can add this to the docs as a forward-looking example.