CodeSampleX

Exemple

dotenv 17.4.2: Existing process.env variables and earlier loaded config files take strict precedence unless override: true or DOTENV_CONFIG_OVERRIDE is explicitly enabled

Échantillon vérifié pour npm dotenv 17.4.2: Existing process.env variables and earlier loaded config files take strict precedence unless override: true or…

sha256:cc2a38a595028c0eb69e9cc8c1b0ea4df797c07aa2d8bfd5af537ec850ba4895

Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré. Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes. MIT-0

Preuves d'exécution

L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.

Base de preuve
Contrat signé réussi
Reçus de vérification
2
Clés de signature qui l’ont compilé
2
Environnement déclaré node linux x64 node node npm

Environnements des exécutions de vérification

Environnement Contrat Étapes Exécution
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-16
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Cas

HOW
Objectif
Existing process.env variables and earlier loaded config files take strict precedence unless override: true or DOTENV_CONFIG_OVERRIDE is explicitly enabled
Paquets
Symboles
  • dotenv.config
  • dotenv/config
  • dotenv.populate
Environnement
node
Créé
2026-08-16T07:07:18Z

Contrat

  1. preset process.env variables are preserved when dotenv.config() runs with default override: false
  2. dotenv.config() returns parsed dictionary from file while process.env retains preexisting value
  3. subsequent dotenv.config() calls silently ignore overlapping keys without override: true
  4. passing override: true overwrites preexisting process.env values with values from file
  5. preloading via dotenv/config respects DOTENV_CONFIG_OVERRIDE=true to overwrite shell environment

Fichiers

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/index.mjs
  • test/contract.mjs

Télécharger l’artefact source (tar.gz)

Code source

NOTES.md
# Notes on dotenv override configuration trap

### search_known_solution Result
`search_known_solution` returned existing sample `sha256:51dd5f35fb9c7be3bc11ebc83f2c01779e197c88f316ecd1ff3173bacbe66884` ("process.env is undefined in an ESM module even though dotenv.config() ran and .env exists", covering ESM static import evaluation order).

### What a model would have written instead
A model would write sequential `dotenv.config({ path: '.env' })` followed by `dotenv.config({ path: '.env.local' })` (or expect `.env` to overwrite empty/preset container environment variables) expecting later files or `.env` values to overwrite `process.env` by default without passing `{ override: true }`.

### How the wrong version fails
Silently with a green build — `dotenv.config()` succeeds without error, `result.error` is `undefined`, and `result.parsed` shows the new values from disk, but `process.env` silently retains the preexisting/empty values because `override` defaults to `false`.
csx.json
{"case":{"caseId":"case:sha256:9f7167990b320cecb53c269f7a61ed89ed0b0bc74caeb5f4bf498730c1637917","contract":["preset process.env variables are preserved when dotenv.config() runs with default override: false","dotenv.config() returns parsed dictionary from file while process.env retains preexisting value","subsequent dotenv.config() calls silently ignore overlapping keys without override: true","passing override: true overwrites preexisting process.env values with values from file","preloading via dotenv/config respects DOTENV_CONFIG_OVERRIDE=true to overwrite shell environment"],"goal":"Existing process.env variables and earlier loaded config files take strict precedence unless override: true or DOTENV_CONFIG_OVERRIDE is explicitly enabled","kind":"HOW","packages":["pkg:npm/dotenv@17.4.2"],"schemaVersion":1,"symbols":["dotenv.config","dotenv/config","dotenv.populate"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/dotenv@17.4.2"],"schemaVersion":1,"symbols":["dotenv.config","dotenv/config","dotenv.populate"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-dotenv-override-config",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-dotenv-override-config",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "dotenv": "17.4.2"
      }
    },
    "node_modules/dotenv": {
      "version": "17.4.2",
      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
      "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://dotenvx.com"
      }
    }
  }
}
package.json
{
  "name": "csx-sample-dotenv-override-config",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "dotenv": "17.4.2"
  }
}
src/index.mjs
import dotenv from 'dotenv';

/**
 * Loads a .env configuration file into process.env.
 * By default (override: false), existing process.env variables are preserved.
 * Pass override: true to allow file variables to overwrite existing ones.
 *
 * @param {import('dotenv').DotenvConfigOptions} [options]
 * @returns {import('dotenv').DotenvConfigOutput}
 */
export function loadEnv(options = {}) {
  return dotenv.config({ quiet: true, ...options });
}

/**
 * Loads layered .env files sequentially (e.g. ['.env', '.env.local']).
 * If override is false (default), keys defined in earlier files cannot be overwritten by later files.
 * If override is true, keys defined in later files overwrite earlier keys.
 *
 * @param {string[]} paths - Array of file paths to load in order
 * @param {boolean} [override=false] - Whether subsequent files overwrite existing process.env keys
 * @returns {Array<import('dotenv').DotenvConfigOutput>}
 */
export function loadLayeredEnv(paths, override = false) {
  const results = [];
  for (const envPath of paths) {
    const res = dotenv.config({ path: envPath, override, quiet: true });
    results.push(res);
  }
  return results;
}

export { dotenv };
test/contract.mjs
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadEnv, loadLayeredEnv } from '../src/index.mjs';

const root = fileURLToPath(new URL('..', import.meta.url));

const envMainPath = path.join(root, '.env.contract-main');
const envBasePath = path.join(root, '.env.contract-base');
const envLocalPath = path.join(root, '.env.contract-local');

// 1. Setup temporary fixture files with permitted example hostnames
fs.writeFileSync(
  envMainPath,
  'API_ENDPOINT=https://api.example.org/v2\nEMPTY_TOKEN=super-secret-token\nNEW_KEY=fresh-value\n',
);
fs.writeFileSync(
  envBasePath,
  'DB_HOST=db.example.test\nDB_PORT=5432\nBASE_ONLY=base-config\n',
);
fs.writeFileSync(
  envLocalPath,
  'DB_HOST=localhost\nLOCAL_ONLY=local-config\n',
);

try {
  // -------------------------------------------------------------------------
  // Test 1: Existing process.env variables take strict precedence by default (override: false)
  // -------------------------------------------------------------------------
  process.env.API_ENDPOINT = 'https://api.example.com/v1';
  process.env.EMPTY_TOKEN = '';
  delete process.env.NEW_KEY;

  const resDefault = loadEnv({ path: envMainPath });

  // Parsing succeeds without error
  assert.equal(resDefault.error, undefined, 'dotenv.config() must not report an error');
  assert.ok(resDefault.parsed, 'dotenv.config() returns parsed key-value map');
  assert.equal(
    resDefault.parsed.API_ENDPOINT,
    'https://api.example.org/v2',
    'result.parsed reflects file value even when process.env is not modified',
  );
  assert.equal(resDefault.parsed.EMPTY_TOKEN, 'super-secret-token');
  assert.equal(resDefault.parsed.NEW_KEY, 'fresh-value');

  // But process.env silently preserves preexisting values (including empty strings)
  assert.equal(
    process.env.API_ENDPOINT,
    'https://api.example.com/v1',
    'default override: false must NOT overwrite existing process.env variables',
  );
  assert.equal(
    process.env.EMPTY_TOKEN,
    '',
    'empty string in process.env counts as defined and is not overwritten',
  );
  assert.equal(
    process.env.NEW_KEY,
    'fresh-value',
    'previously unset keys are populated normally',
  );

  // -------------------------------------------------------------------------
  // Test 2: Passing override: true explicitly allows overwriting process.env
  // -------------------------------------------------------------------------
  const resOverride = loadEnv({ path: envMainPath, override: true });
  assert.equal(resOverride.error, undefined);
  assert.equal(
    process.env.API_ENDPOINT,
    'https://api.example.org/v2',
    'override: true must update process.env with the file value',
  );
  assert.equal(
    process.env.EMPTY_TOKEN,
    'super-secret-token',
    'override: true must overwrite empty string presets in process.env',
  );

  // -------------------------------------------------------------------------
  // Test 3: Layered configuration files (.env.base + .env.local)
  // -------------------------------------------------------------------------
  // 3a. Without override: true, the second file (.env.local) silently fails to override keys from .env.base
  delete process.env.DB_HOST;
  delete process.env.DB_PORT;
  delete process.env.BASE_ONLY;
  delete process.env.LOCAL_ONLY;

  loadLayeredEnv([envBasePath, envLocalPath], false);

  assert.equal(
    process.env.DB_HOST,
    'db.example.test',
    'without override: true, .env.local cannot overwrite DB_HOST set by .env.base',
  );
  assert.equal(process.env.DB_PORT, '5432');
  assert.equal(process.env.BASE_ONLY, 'base-config');
  assert.equal(
    process.env.LOCAL_ONLY,
    'local-config',
    'new keys from .env.local are still added',
  );

  // 3b. With override: true, .env.local successfully overrides overlapping keys from .env.base
  delete process.env.DB_HOST;
  delete process.env.DB_PORT;
  delete process.env.BASE_ONLY;
  delete process.env.LOCAL_ONLY;

  loadLayeredEnv([envBasePath, envLocalPath], true);

  assert.equal(
    process.env.DB_HOST,
    'localhost',
    'with override: true, .env.local overrides DB_HOST',
  );
  assert.equal(process.env.DB_PORT, '5432', 'unaltered keys from .env.base remain intact');
  assert.equal(process.env.BASE_ONLY, 'base-config');
  assert.equal(process.env.LOCAL_ONLY, 'local-config');

  // -------------------------------------------------------------------------
  // Test 4: Preloading with node -r dotenv/config and DOTENV_CONFIG_OVERRIDE
  // -------------------------------------------------------------------------
  // 4a. Without DOTENV_CONFIG_OVERRIDE, shell environment variables win over preloaded file
  const childEnvWithoutOverride = {
    ...process.env,
    DB_HOST: 'shell.example.test',
    DOTENV_CONFIG_PATH: envBasePath,
    DOTENV_CONFIG_QUIET: 'true',
  };
  delete childEnvWithoutOverride.DOTENV_CONFIG_OVERRIDE;

  const r1 = spawnSync(
    process.execPath,
    ['-r', 'dotenv/config', '-e', 'process.stdout.write(process.env.DB_HOST)'],
    { cwd: root, env: childEnvWithoutOverride, encoding: 'utf8' },
  );
  assert.equal(r1.status, 0, `preload failed: ${r1.stderr}`);
  assert.equal(
    r1.stdout.trim(),
    'shell.example.test',
    'preloading without DOTENV_CONFIG_OVERRIDE leaves shell variable intact',
  );

  // 4b. With DOTENV_CONFIG_OVERRIDE=true, file values override shell environment variables
  const childEnvWithOverride = {
    ...process.env,
    DB_HOST: 'shell.example.test',
    DOTENV_CONFIG_PATH: envBasePath,
    DOTENV_CONFIG_OVERRIDE: 'true',
    DOTENV_CONFIG_QUIET: 'true',
  };

  const r2 = spawnSync(
    process.execPath,
    ['-r', 'dotenv/config', '-e', 'process.stdout.write(process.env.DB_HOST)'],
    { cwd: root, env: childEnvWithOverride, encoding: 'utf8' },
  );
  assert.equal(r2.status, 0, `preload with override failed: ${r2.stderr}`);
  assert.equal(
    r2.stdout.trim(),
    'db.example.test',
    'preloading with DOTENV_CONFIG_OVERRIDE=true updates process.env to file value',
  );

  console.log(
    'CONTRACT PASS: dotenv preserves existing process.env variables unless override: true or DOTENV_CONFIG_OVERRIDE is enabled',
  );
} finally {
  fs.rmSync(envMainPath, { force: true });
  fs.rmSync(envBasePath, { force: true });
  fs.rmSync(envLocalPath, { force: true });
}

Seeder d'origine

csx-seed