@dotenvx/dotenvx 2.17.1 → 2.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.17.1...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.17.3...main)
6
+
7
+ ## [2.17.3](https://github.com/dotenvx/dotenvx/compare/v2.17.2...v2.17.3) (2026-07-23)
8
+
9
+ ### Changed
10
+
11
+ * Exempt `.env.example` in subdirectories from precommit ([#920](https://github.com/dotenvx/dotenvx/pull/920))
12
+
13
+ ## [2.17.2](https://github.com/dotenvx/dotenvx/compare/v2.17.1...v2.17.2) (2026-07-22)
14
+
15
+ ### Added
16
+
17
+ * Add support for `DOTENV_CONFIG_IGNORE` global convenience ([#919](https://github.com/dotenvx/dotenvx/pull/919))
6
18
 
7
19
  ## [2.17.1](https://github.com/dotenvx/dotenvx/compare/v2.17.0...v2.17.1) (2026-07-21)
8
20
 
package/README.md CHANGED
@@ -955,24 +955,6 @@ $ dotenvx encrypt --stdout > .env.encrypted
955
955
  > If your organization's compliance department requires [NIST approved curves](https://csrc.nist.gov/projects/elliptic-curve-cryptography) or other curves like `curve25519`, please reach out at [security@dotenvx.com](mailto:security@dotenvx.com).
956
956
 
957
957
  </details>
958
- <details><summary>agents</summary><br>
959
-
960
- After encryption, `DOTENV_PUBLIC_KEY` lives in your encrypted `.env` file. This means agents and automation can keep running `dotenvx set` and `dotenvx encrypt` without reading `.env.keys`.
961
-
962
- ```sh
963
- $ chmod a-r .env.keys
964
-
965
- $ dotenvx set HELLO World
966
- ◈ encrypted HELLO (.env)
967
-
968
- $ dotenvx encrypt
969
- ◈ encrypted (.env)
970
- ```
971
-
972
- Keep `.env.keys` unreadable by agents, while still letting them safely update encrypted values.
973
-
974
- </details>
975
-
976
958
  &nbsp;
977
959
 
978
960
  ## Advanced
@@ -1586,6 +1568,11 @@ $ dotenvx run -f .env.missing --ignore=MISSING_ENV_FILE -- node index.js
1586
1568
  ...
1587
1569
  ```
1588
1570
 
1571
+ You can also set `DOTENV_CONFIG_IGNORE="MISSING_ENV_FILE"`. It is parsed as a comma-separated list.
1572
+
1573
+ ```sh
1574
+ $ DOTENV_CONFIG_IGNORE="MISSING_ENV_FILE,OTHER_ERROR_CODE" dotenvx run -f .env.missing -- node index.js
1575
+ ```
1589
1576
  </details>
1590
1577
  <details><summary>`run --convention=nextjs`</summary><br>
1591
1578
 
@@ -2016,8 +2003,6 @@ $ dotenvx set HELLO World
2016
2003
  set HELLO with encryption (.env)
2017
2004
  ```
2018
2005
 
2019
- Works with unreadable `.env.keys` when `.env` already contains `DOTENV_PUBLIC_KEY`.
2020
-
2021
2006
  </details>
2022
2007
  <details><summary>`set KEY value -f`</summary><br>
2023
2008
 
@@ -2129,8 +2114,6 @@ $ dotenvx encrypt
2129
2114
  ⮕ next run [DOTENV_PRIVATE_KEY='122...0b8' dotenvx run -- yourcommand] to test decryption locally
2130
2115
  ```
2131
2116
 
2132
- Works with unreadable `.env.keys` when `.env` already contains `DOTENV_PUBLIC_KEY`.
2133
-
2134
2117
  </details>
2135
2118
  <details><summary>`encrypt -f`</summary><br>
2136
2119
 
@@ -2596,6 +2579,27 @@ Use `-f` and `-fk` to validate a specific env file and keys file. The command ex
2596
2579
  $ dotenvx validate -f .env.production -fk .env.keys
2597
2580
  ```
2598
2581
 
2582
+ </details>
2583
+ <details><summary>`validate --ignore`</summary><br>
2584
+
2585
+ Ignore specific validation error codes.
2586
+
2587
+ ```sh
2588
+ $ dotenvx validate --ignore=MISSING_ENV_EXAMPLE
2589
+ ```
2590
+
2591
+ Ignore multiple error codes by separating them with spaces.
2592
+
2593
+ ```sh
2594
+ $ dotenvx validate --ignore=MISSING_ENV_FILE MISSING_ENV_EXAMPLE
2595
+ ```
2596
+
2597
+ You can also set `DOTENV_CONFIG_IGNORE`. Its value is a comma-separated list.
2598
+
2599
+ ```sh
2600
+ $ DOTENV_CONFIG_IGNORE=MISSING_ENV_FILE,OTHER dotenvx validate
2601
+ ```
2602
+
2599
2603
  </details>
2600
2604
  <details><summary>`genexample`</summary><br>
2601
2605
 
@@ -3039,6 +3043,8 @@ X.X.X
3039
3043
 
3040
3044
  </details>
3041
3045
 
3046
+ &nbsp;
3047
+
3042
3048
  ### Library 📦
3043
3049
 
3044
3050
  Use dotenvx directly in code.
@@ -3491,6 +3497,29 @@ Set `mask: 0` to fully mask values.
3491
3497
 
3492
3498
  &nbsp;
3493
3499
 
3500
+ ### Settings ⚙️
3501
+
3502
+ There are global settings available that can be configured as environment variables.
3503
+
3504
+ <details><summary>Environment variables</summary><br>
3505
+
3506
+ ```ini
3507
+ # dotenvx native settings
3508
+ DOTENV_CONFIG_CONVENTION= # set to a default convention like 'nextjs' or 'flow'
3509
+ DOTENV_CONFIG_IGNORE= # MISSING_ENV_FILE,OTHER
3510
+ DOTENV_CONFIG_QUIET= # set to "true" to default to --quiet
3511
+ DOTENVX_NO_ARMOR= # set to "true" to turn off Armor support
3512
+ DOTENVX_NO_1PASSWORD= # set to "true" to turn off 1Password support
3513
+ DOTENVX_NO_BITWARDEN= # set to "true" to turn off Bitwarden support
3514
+
3515
+ # other machine settings respected by dotenvx
3516
+ BW_SESSION= # set to bitwarden session token to bypass password prompt
3517
+ ```
3518
+
3519
+ </details>
3520
+
3521
+ &nbsp;
3522
+
3494
3523
  ## Armor ⛨
3495
3524
 
3496
3525
  [![dotenvx-armor](https://dotenvx.com/dotenvx-armor-banner.png?v2)](https://dotenvx.com/armor)
@@ -3513,6 +3542,7 @@ Set `mask: 0` to fully mask values.
3513
3542
  * [Claude](https://dotenvx.com/docs/secrets-in-claude)
3514
3543
  * [Codex](https://dotenvx.com/docs/secrets-in-codex)
3515
3544
  * [1Password](https://dotenvx.com/docs/secrets-in-1password)
3545
+ * [Bitwarden](https://dotenvx.com/docs/secrets-in-bitwarden)
3516
3546
  * [Node.js](https://dotenvx.com/docs/secrets-in-nodejs)
3517
3547
  * [Next.js](https://dotenvx.com/docs/secrets-in-nextjs)
3518
3548
  * [Express](https://dotenvx.com/docs/secrets-in-express)
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.17.1",
2
+ "version": "2.17.3",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -6,13 +6,14 @@ const createSpinner = require('../../lib/helpers/createSpinner')
6
6
  const Session = require('../../db/session')
7
7
  const getResolver = require('./../../lib/resolvers/get')
8
8
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
9
+ const normalizeDotenvConfigIgnore = require('../../lib/helpers/normalizeDotenvConfigIgnore')
9
10
  const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
10
11
  const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
11
12
  const mask = require('../../lib/helpers/mask')
12
13
  const filterKeys = require('../../lib/helpers/filterKeys')
13
14
 
14
15
  async function get (key) {
15
- const options = normalizeDotenvConfigConvention(this.opts())
16
+ const options = normalizeDotenvConfigIgnore(normalizeDotenvConfigConvention(this.opts()))
16
17
  const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
17
18
  const spinner = await createSpinner({ ...spinnerOptions, ...options, text: 'decrypting' })
18
19
 
@@ -8,6 +8,7 @@ const createSpinner = require('../../lib/helpers/createSpinner')
8
8
  const Session = require('../../db/session')
9
9
  const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
10
10
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
11
+ const normalizeDotenvConfigIgnore = require('../../lib/helpers/normalizeDotenvConfigIgnore')
11
12
  const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
12
13
  const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
13
14
  const mask = require('../../lib/helpers/mask')
@@ -53,7 +54,7 @@ function uniqueInjectedKeys (processedEnvs) {
53
54
  }
54
55
 
55
56
  async function run () {
56
- const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
57
+ const options = normalizeDotenvConfigIgnore(normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts())))
57
58
  const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
58
59
  const maskEnabled = options.mask !== undefined
59
60
  const redactEnabled = options.redact === true
@@ -6,6 +6,7 @@ const createSpinner = require('../../lib/helpers/createSpinner')
6
6
  const Session = require('../../db/session')
7
7
  const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
8
8
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
9
+ const normalizeDotenvConfigIgnore = require('../../lib/helpers/normalizeDotenvConfigIgnore')
9
10
  const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
10
11
  const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
11
12
  const validateEnvExample = require('../../lib/helpers/validateEnvExample')
@@ -13,7 +14,7 @@ const validateEnvExample = require('../../lib/helpers/validateEnvExample')
13
14
  const { determine } = require('./../../lib/helpers/envResolution')
14
15
 
15
16
  async function validate () {
16
- const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
17
+ const options = normalizeDotenvConfigIgnore(normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts())))
17
18
  const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
18
19
  const spinner = await createSpinner({ ...spinnerOptions, ...options, text: 'validating' })
19
20
  const ignore = options.ignore || []
@@ -1,4 +1,6 @@
1
- function normalizeDotenvConfigConvention (options) {
1
+ function normalizeDotenvConfigConvention (
2
+ /** @type {import('../main').DotenvConfigOptions} */ options
3
+ ) {
2
4
  if (!options.convention && process.env.DOTENV_CONFIG_CONVENTION) {
3
5
  options.convention = process.env.DOTENV_CONFIG_CONVENTION
4
6
  }
@@ -0,0 +1,17 @@
1
+ function normalizeDotenvConfigIgnore (
2
+ /** @type {import('../main').DotenvConfigOptions} */ options
3
+ ) {
4
+ const configIgnore = (process.env.DOTENV_CONFIG_IGNORE || '')
5
+ .split(',')
6
+ .map(code => code.trim())
7
+ .filter(Boolean)
8
+
9
+ if (configIgnore.length < 1) return options
10
+
11
+ return {
12
+ ...options,
13
+ ignore: [...(options.ignore || []), ...configIgnore]
14
+ }
15
+ }
16
+
17
+ module.exports = normalizeDotenvConfigIgnore
@@ -1,4 +1,6 @@
1
- function normalizeDotenvConfigQuiet (options) {
1
+ function normalizeDotenvConfigQuiet (
2
+ /** @type {import('../main').DotenvConfigOptions} */ options
3
+ ) {
2
4
  if (process.env.DOTENV_CONFIG_QUIET === 'true') {
3
5
  options.quiet = true
4
6
  }
package/src/lib/main.js CHANGED
@@ -26,6 +26,7 @@ const decryptKeyValue = require('./helpers/cryptography/decryptKeyValue')
26
26
  const Errors = require('./helpers/errors')
27
27
  const normalizeDotenvConfigQuiet = require('./helpers/normalizeDotenvConfigQuiet')
28
28
  const normalizeDotenvConfigConvention = require('./helpers/normalizeDotenvConfigConvention')
29
+ const normalizeDotenvConfigIgnore = require('./helpers/normalizeDotenvConfigIgnore')
29
30
  const mask = require('./helpers/mask')
30
31
  const maskProcessedEnvs = require('./helpers/maskProcessedEnvs')
31
32
 
@@ -43,6 +44,7 @@ function uniqueInjectedKeys (processedEnvs) {
43
44
  const config = function (options = {}) {
44
45
  options = normalizeDotenvConfigQuiet(options)
45
46
  options = normalizeDotenvConfigConvention(options)
47
+ options = normalizeDotenvConfigIgnore(options)
46
48
 
47
49
  // allow user to set processEnv to write to
48
50
  let processEnv = process.env
@@ -318,6 +320,7 @@ const set = async function (key, value, options = {}) {
318
320
  /* @type {import('./main').get} */
319
321
  const get = async function (key, options = {}) {
320
322
  options = normalizeDotenvConfigConvention(options)
323
+ options = normalizeDotenvConfigIgnore(options)
321
324
 
322
325
  const envs = buildEnvs(options)
323
326
  const noArmor = resolveNoArmor(options)
@@ -301,6 +301,8 @@ async function envs (options = {}) {
301
301
  const readableFilepaths = new Set()
302
302
  const processEnv = options.processEnv || process.env
303
303
  const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
304
+ const no1Password = options.no1Password === true || process.env.DOTENVX_NO_1PASSWORD === 'true'
305
+ const noBitwarden = options.noBitwarden === true || process.env.DOTENVX_NO_BITWARDEN === 'true'
304
306
  const provider = await providers(options)
305
307
  const decryptor = await decryptors(options)
306
308
  for (const env of options.envs || []) {
@@ -313,8 +315,8 @@ async function envs (options = {}) {
313
315
  provider,
314
316
  decryptor,
315
317
  readableFilepaths,
316
- no1Password: options.no1Password,
317
- noBitwarden: options.noBitwarden,
318
+ no1Password,
319
+ noBitwarden,
318
320
  onStatus: options.onStatus
319
321
  }))
320
322
  } else if (env.type === TYPE_ENV) {
@@ -325,8 +327,8 @@ async function envs (options = {}) {
325
327
  envKeysFilepath,
326
328
  provider,
327
329
  decryptor,
328
- no1Password: options.no1Password,
329
- noBitwarden: options.noBitwarden,
330
+ no1Password,
331
+ noBitwarden,
330
332
  onStatus: options.onStatus
331
333
  }))
332
334
  }
@@ -343,6 +345,8 @@ function envsSync (options = {}) {
343
345
  const readableFilepaths = new Set()
344
346
  const processEnv = options.processEnv || process.env
345
347
  const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
348
+ const no1Password = options.no1Password === true || process.env.DOTENVX_NO_1PASSWORD === 'true'
349
+ const noBitwarden = options.noBitwarden === true || process.env.DOTENVX_NO_BITWARDEN === 'true'
346
350
  const provider = providers.sync(options)
347
351
  const decryptor = decryptors.sync(options)
348
352
 
@@ -356,8 +360,8 @@ function envsSync (options = {}) {
356
360
  provider,
357
361
  decryptor,
358
362
  readableFilepaths,
359
- no1Password: options.no1Password,
360
- noBitwarden: options.noBitwarden
363
+ no1Password,
364
+ noBitwarden
361
365
  }))
362
366
  } else if (env.type === TYPE_ENV) {
363
367
  processedEnvs.push(injectEnvSync({
@@ -367,8 +371,8 @@ function envsSync (options = {}) {
367
371
  envKeysFilepath,
368
372
  provider,
369
373
  decryptor,
370
- no1Password: options.no1Password,
371
- noBitwarden: options.noBitwarden
374
+ no1Password,
375
+ noBitwarden
372
376
  }))
373
377
  }
374
378
  }
@@ -54,12 +54,13 @@ class Precommit {
54
54
  count += 1
55
55
 
56
56
  const file = path.join(this.directory, _file) // to handle when directory argument passed
57
+ const filename = path.basename(file) // exemptions match on filename, so they apply at any depth
57
58
 
58
59
  // check if file is going to be committed
59
60
  if (this._isFileToBeCommitted(file)) {
60
61
  // check if that file is being ignored
61
62
  if (ig.ignores(file)) {
62
- if (file === '.env.example' || file === '.env.x') {
63
+ if (filename === '.env.example' || filename === '.env.x') {
63
64
  const warning = new Errors({
64
65
  message: `${file} ignored (should not be)`,
65
66
  help: `fix: [dotenvx gitignore --pattern !${file}]`
@@ -67,7 +68,7 @@ class Precommit {
67
68
  warnings.push(warning)
68
69
  }
69
70
  } else {
70
- if (file !== '.env.example' && file !== '.env.x') {
71
+ if (filename !== '.env.example' && filename !== '.env.x') {
71
72
  const src = fsx.readFileXSync(file)
72
73
  const encrypted = sealed(src)
73
74