Skip to content

[general-kit]: add --env-file CLI flag for loading dotenv files#5885

Open
h2m6jcm94s-eng wants to merge 1 commit into
drizzle-team:mainfrom
h2m6jcm94s-eng:feat/drizzle-kit-env-file
Open

[general-kit]: add --env-file CLI flag for loading dotenv files#5885
h2m6jcm94s-eng wants to merge 1 commit into
drizzle-team:mainfrom
h2m6jcm94s-eng:feat/drizzle-kit-env-file

Conversation

@h2m6jcm94s-eng

Copy link
Copy Markdown

Motivation

Closes #4588.

drizzle-kit only auto-loads a top-level .env from the current working directory via the import 'dotenv/config' side-effect at the top of src/cli/schema.ts. Anyone keeping their DATABASE_URL in a non-default file (e.g. .env.local, .env.staging) currently has to either:

  • inline the connection string on every invocation (DATABASE_URL=... drizzle-kit migrate), or
  • wrap the CLI in a dotenv -e .env.local -- / tsx --env-file=… shim.

This matches the precedent set by Node 22+'s native --env-file flag and by tsx --env-file=. The issue explicitly asks for multiple-flag support, which this PR delivers.

Behavior

drizzle-kit migrate --env-file=.env.local
drizzle-kit push   --env-file=.env --env-file=.env.local
drizzle-kit generate -e .env.local

Precedence (highest → lowest):

  1. Existing process.env (shell-exported vars) — never overwritten.
  2. The last --env-file on the command line.
  3. Earlier --env-file occurrences.
  4. The auto-loaded .env in the cwd (existing behavior, unchanged).

This mirrors Node 22's semantics: shell vars win, later --env-file overrides earlier --env-file, and .env remains the auto-loaded baseline.

Implementation

  1. New drizzle-kit/src/cli/env-loader.ts — exports extractEnvFiles (argv parser, supports --env-file=path, --env-file path, -e=path, -e path) and applyEnvFiles (loader honoring the precedence above). The module body runs the extraction against process.argv.slice(2), applies the files, and strips the flags from process.argv so brocli (which doesn't share an option across commands) never sees them.
  2. drizzle-kit/src/cli/index.tsimport './env-loader'; as the first statement. Because ES module bindings are evaluated in source order, this fires before ./schema's top-level import 'dotenv/config', ensuring .env (override-false) does not stomp the --env-file values.
  3. drizzle-kit/src/cli/schema.ts — adds optionEnvFile = string('env-file').desc(...) and includes it on every subcommand's options block so it shows up in --help. The handler never reads it (the loader has already consumed it); it's purely documentation.
  4. drizzle-kit/src/cli/validations/common.ts — extends assertCollisions to exclude envFile from the exhaustiveness check the same way config is excluded, since it's an out-of-band option.

Tests

drizzle-kit/tests/cli-env-file.test.ts (vitest, 9 cases):

  • extractEnvFiles: --env-file=path, --env-file path, -e=path, -e path, multiple occurrences in declaration order, no-flag case.
  • applyEnvFiles: single file loads, shell vars not overwritten, later file wins over earlier file, shell var still wins over multi-file overrides.

Existing cli-migrate.test.ts / cli-generate.test.ts continue to pass — verified locally.

Compatibility

  • No new runtime deps; dotenv was already a (dev) dep.
  • Default behavior with no --env-file is byte-for-byte unchanged (the auto-loaded .env still loads from schema.ts).
  • Existing CI patterns that export DATABASE_URL via the shell continue to take precedence over any --env-file.

Adds support for loading environment variables from one or more files
before the drizzle config is evaluated, matching the semantics of Node
22's --env-file flag:

  drizzle-kit migrate --env-file=.env.local
  drizzle-kit push --env-file=.env --env-file=.env.local

The flag is parsed in a new ./env-loader module imported as the first
side-effect in src/cli/index.ts. It runs before src/cli/schema.ts
(whose top-level `import 'dotenv/config'` loads the default .env), so
values from --env-file are visible to the config file alongside any
default .env values.

Precedence (highest to lowest):
  1. Existing process.env (shell-exported vars), never overwritten
  2. The last --env-file on the command line
  3. Earlier --env-file occurrences
  4. The auto-loaded .env (existing behavior)

The flag is stripped from process.argv after parsing so brocli, which
doesn't share an option across every command, never sees it. It is
still registered as an option on every subcommand so it appears in
--help output and assertCollisions ignores it the same way it ignores
--config.

Tests cover the argv extractor (multiple forms, ordering) and the
loader semantics (single file, multi-file precedence, shell-var
protection) in drizzle-kit/tests/cli-env-file.test.ts.

Closes drizzle-team#4588
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Support for Multiple --env-file Flags in Drizzle Kit CLI

1 participant