Skip to content

Multi-Profile .env Support (.env, .env.local, .env.production) + dotenv-expand & Precedence #5

@hoangsonww

Description

@hoangsonww

Summary

Add first-class support for layered env files and variable interpolation so teams can validate realistic setups:

  • Multiple files with deterministic precedence (e.g., .env.env.local.env.production).
  • Optional [dotenv-expand]-style interpolation (e.g., API_URL=${HOST}/api).
  • Clear reporting of the effective value and the source file for each key.

Why

Real projects rarely use a single .env. Without layering, validation diverges from production behavior and misses override bugs. Interpolation is common and currently unvalidated.

Proposal

New options

validateEnv({
  schema,
  files: [".env", ".env.local", ".env.production"], // left→right, later wins
  precedence: "right-most-wins",                    // default
  expand: true,                                     // enable ${VAR} interpolation
  expandErrorOnMissing: true,                       // fail if referenced var is missing
  expandDetectCycles: true                          // protect against recursive loops
});

CLI

env-guard \
  --schema schema.json \
  --files .env,.env.local,.env.production \
  --expand \
  --expand-error-on-missing \
  --expand-detect-cycles

Behavior

  • Load files in order; later files override earlier ones.
  • Interpolate ${VAR} after merge, before schema checks.
  • On report, include source (which file provided the winning value).
  • Mask secret values in all output (keep existing masking rules).

Examples

.env

HOST=https://example.com
PORT=3000

.env.local

PORT=3001

.env.production

API_URL=${HOST}/api

Effective:

  • HOST = https://example.com (source: .env)
  • PORT = 3001 (source: .env.local)
  • API_URL = https://example.com/api (source: .env.production)

Schema sample

const schema = {
  HOST: { required: true, regex: /^https?:\/\// },
  PORT: { required: true, type: "int", min: 1, max: 65535 },
  API_URL: { requiredIf: { NODE_ENV: ["production"] }, regex: /^https?:\/\/.+\/api/ }
};

Errors & Edge Cases

  • Missing referenced var (expandErrorOnMissing: true): throw with VAR_NOT_DEFINED and show chain.
  • Interpolation cycle (A=${B}, B=${A} with expandDetectCycles: true): throw with CYCLE_DETECTED, include trace.
  • Shadowing warning: if the same key exists in multiple files, log a note indicating overridden sources (non-fatal).

Reporting

Add to JSON/MD output:

{
  "key": "API_URL",
  "valueMasked": "https://example.com/api",
  "source": ".env.production",
  "overrides": [".env"]
}

Acceptance Criteria

  • Loading multiple files with deterministic right-most precedence.
  • Interpolation of ${VAR} with optional strict missing detection.
  • Cycle detection with human-readable traces.
  • Reports include source and overrides metadata.
  • CLI flags as specified; docs updated with examples.
  • 95%+ test coverage for merge, expand, cycles, error paths.

Nice-to-have (optional in follow-ups)

  • Support ${VAR:-default} fallback syntax.
  • Per-file glob patterns (e.g., .env.*.local).

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is neededquestionFurther information is requested

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions