@dotenvx/dotenvx 2.3.4 → 2.4.0

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,14 @@
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.3.4...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.4.0...main)
6
+
7
+ ## [2.4.0](https://github.com/dotenvx/dotenvx/compare/v2.3.4...v2.4.0) (2026-07-10)
8
+
9
+ ### Added
10
+
11
+ * Support multiple `-fk` flags for select commands ([#880](https://github.com/dotenvx/dotenvx/pull/880))
12
+ * Support `-f ../..` directories ([#881](https://github.com/dotenvx/dotenvx/pull/881))
6
13
 
7
14
  ## [2.3.4](https://github.com/dotenvx/dotenvx/compare/v2.3.3...v2.3.4) (2026-07-09)
8
15
 
package/README.md CHANGED
@@ -843,25 +843,45 @@ Hello Dotenvx
843
843
  Note the `DOTENV_PRIVATE_KEY` instructs `dotenvx run` to load the `.env` file and the `DOTENV_PRIVATE_KEY_PRODUCTION` instructs it to load the `.env.production` file. See the pattern?
844
844
 
845
845
  </details>
846
- <details><summary>combine multiple encrypted .env files for monorepo</summary><br>
846
+ <details><summary>use directories with monorepos</summary><br>
847
+
848
+ Point `-f` at a directory to load the `.env` inside it. From a workspace, this makes a shared root `.env` available without repeating its filename.
849
+
850
+ ```text
851
+ my-monorepo/
852
+ .env
853
+ .env.keys
854
+ apps/
855
+ web/
856
+ index.js
857
+ ```
847
858
 
848
859
  ```sh
849
- $ mkdir app1
850
- $ mkdir app2
851
- $ dotenvx set HELLO app1 -f app1/.env.ci
852
- $ dotenvx set HELLO app2 -f app2/.env.ci
853
- $ echo "console.log('Hello ' + process.env.HELLO)" > index.js
860
+ $ cd apps/web
854
861
 
855
- $ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci -- node index.js
856
- [dotenvx@1.X.X] injecting env (2) from app1/.env.ci,app2/.env.ci
857
- Hello app1
862
+ $ dotenvx get HELLO -f ../..
863
+ World
858
864
 
859
- $ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci --overload -- node index.js
860
- [dotenvx@1.X.X] injecting env (2) from app1/.env.ci,app2/.env.ci
861
- Hello app2
865
+ $ dotenvx run -f ../.. -- node index.js
866
+ [dotenvx@1.X.X] injecting env (1) from ../../.env
867
+ Hello World
868
+ ```
869
+
870
+ Encrypted values work without extra configuration when `.env.keys` sits beside the resolved `.env`.
871
+
872
+ The directory also becomes the base when using a convention:
873
+
874
+ ```sh
875
+ $ dotenvx run -f ../.. --convention=nextjs -- node index.js
876
+ [dotenvx@1.X.X] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
877
+ Hello development local
862
878
  ```
863
879
 
864
- Note the `DOTENV_PRIVATE_KEY_CI` (and any `DOTENV_PRIVATE_KEY*`) can take multiple private keys by simply comma separating them.
880
+ If a workspace has its own `.env` but shares the root `.env.keys`, point `-fk` at the root directory:
881
+
882
+ ```sh
883
+ $ dotenvx run -f . -fk ../.. -- node index.js
884
+ ```
865
885
 
866
886
  </details>
867
887
  <details><summary>`--stdout`</summary><br>
@@ -1108,6 +1128,17 @@ $ dotenvx get GOODBYE
1108
1128
  [MISSING_KEY] missing key (GOODBYE)
1109
1129
  ```
1110
1130
 
1131
+ </details>
1132
+ <details><summary>`run -f <directory>`</summary><br>
1133
+
1134
+ Run a command using the `.env` file in a directory. This is useful with monorepos.
1135
+
1136
+ ```sh
1137
+ $ dotenvx run -f ../.. -- node index.js
1138
+ [dotenvx@1.X.X] injecting env (1) from ../../.env
1139
+ Hello World
1140
+ ```
1141
+
1111
1142
  </details>
1112
1143
  <details><summary>`run` - multiple `-f` flags</summary><br>
1113
1144
 
@@ -1383,6 +1414,17 @@ Hello development local
1383
1414
 
1384
1415
  (more conventions available upon request)
1385
1416
 
1417
+ </details>
1418
+ <details><summary>`run -f <directory> --convention=nextjs`</summary><br>
1419
+
1420
+ Run a command using Next.js' convention from a directory. This is useful with monorepos.
1421
+
1422
+ ```sh
1423
+ $ dotenvx run -f ../.. --convention=nextjs -- node index.js
1424
+ [dotenvx@1.X.X] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
1425
+ Hello development local
1426
+ ```
1427
+
1386
1428
  </details>
1387
1429
  <details><summary>`run --convention=flow`</summary><br>
1388
1430
 
@@ -1490,6 +1532,16 @@ $ dotenvx get HELLO -f .env.production
1490
1532
  production
1491
1533
  ```
1492
1534
 
1535
+ </details>
1536
+ <details><summary>`get KEY -f <directory>`</summary><br>
1537
+
1538
+ Return a single environment variable's value from the `.env` file in a directory. This is useful with monorepos.
1539
+
1540
+ ```sh
1541
+ $ dotenvx get HELLO -f ../..
1542
+ World
1543
+ ```
1544
+
1493
1545
  </details>
1494
1546
  <details><summary>`get KEY -fk`</summary><br>
1495
1547
 
@@ -1561,6 +1613,16 @@ $ DOTENV_CONFIG_CONVENTION=nextjs dotenvx get HELLO
1561
1613
  development local
1562
1614
  ```
1563
1615
 
1616
+ </details>
1617
+ <details><summary>`get KEY -f <directory> --convention=nextjs`</summary><br>
1618
+
1619
+ Return a single environment variable's value using Next.js' convention from a directory. This is useful with monorepos.
1620
+
1621
+ ```sh
1622
+ $ dotenvx get HELLO -f ../.. --convention=nextjs
1623
+ development local
1624
+ ```
1625
+
1564
1626
  </details>
1565
1627
  <details><summary>`get KEY --convention=flow`</summary><br>
1566
1628
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.3.4",
2
+ "version": "2.4.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -1,12 +1,13 @@
1
1
  const { logger } = require('./../../shared/logger')
2
2
 
3
- const conventions = require('./../../lib/helpers/conventions')
4
3
  const escape = require('./../../lib/helpers/escape')
5
4
  const catchAndLog = require('./../../lib/helpers/catchAndLog')
6
5
  const createSpinner = require('../../lib/helpers/createSpinner')
7
6
  const Session = require('../../db/session')
8
7
  const getResolver = require('./../../lib/resolvers/get')
9
8
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
9
+ const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
10
+ const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
10
11
 
11
12
  async function get (key) {
12
13
  const options = normalizeDotenvConfigConvention(this.opts())
@@ -21,13 +22,7 @@ async function get (key) {
21
22
  const ignore = options.ignore || []
22
23
  let errorCount = 0
23
24
 
24
- let envs = []
25
- // handle shorthand conventions - like --convention=nextjs
26
- if (options.convention) {
27
- envs = conventions(options.convention).concat(this.envs)
28
- } else {
29
- envs = this.envs
30
- }
25
+ const envs = buildCommandEnvs(this.envs, options.convention)
31
26
 
32
27
  try {
33
28
  const sesh = new Session()
@@ -38,7 +33,7 @@ async function get (key) {
38
33
  envs,
39
34
  overload: options.overload,
40
35
  all: options.all,
41
- envKeysFile: options.envKeysFile,
36
+ envKeysFile: resolveEnvKeysFile(options.envKeysFile),
42
37
  noArmor,
43
38
  noKeychain,
44
39
  onStatus: (text) => {
@@ -8,8 +8,9 @@ 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 buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
12
+ const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
11
13
 
12
- const conventions = require('./../../lib/helpers/conventions')
13
14
  const { determine } = require('./../../lib/helpers/envResolution')
14
15
 
15
16
  function inferCommandArgsFromProcessArgv (argv) {
@@ -80,13 +81,7 @@ async function run () {
80
81
  }
81
82
 
82
83
  try {
83
- let envs = []
84
- // handle shorthand conventions - like --convention=nextjs
85
- if (options.convention) {
86
- envs = conventions(options.convention).concat(this.envs)
87
- } else {
88
- envs = this.envs
89
- }
84
+ let envs = buildCommandEnvs(this.envs, options.convention)
90
85
  envs = determine(envs, process.env)
91
86
 
92
87
  const {
@@ -96,7 +91,7 @@ async function run () {
96
91
  envs,
97
92
  overload: options.overload,
98
93
  processEnv: process.env,
99
- envKeysFile: options.envKeysFile,
94
+ envKeysFile: resolveEnvKeysFile(options.envKeysFile),
100
95
  noArmor,
101
96
  noKeychain,
102
97
  token: options.token,
@@ -17,10 +17,17 @@ const envs = []
17
17
  function collectEnvs (type) {
18
18
  return function (value, previous) {
19
19
  envs.push({ type, value })
20
- return previous.concat([value])
20
+ return (previous || []).concat([value])
21
21
  }
22
22
  }
23
23
 
24
+ function collectEnvKeys (value, previous) {
25
+ if (previous === undefined) return value
26
+ if (Array.isArray(previous)) return previous.concat([value])
27
+
28
+ return [previous, value]
29
+ }
30
+
24
31
  // global log levels
25
32
  program
26
33
  .usage('run -- yourcommand')
@@ -56,7 +63,7 @@ program.command('run')
56
63
  .addHelpText('after', examples.run)
57
64
  .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
58
65
  .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
59
- .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
66
+ .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
60
67
  .option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
61
68
  .option('--strict', 'process.exit(1) on any errors', false)
62
69
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
@@ -76,7 +83,7 @@ program.command('get')
76
83
  .argument('[KEY]', 'environment variable name')
77
84
  .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
78
85
  .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
79
- .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
86
+ .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
80
87
  .option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
81
88
  .option('--strict', 'process.exit(1) on any errors', false)
82
89
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
@@ -133,7 +140,7 @@ program.command('encrypt')
133
140
  program.command('decrypt')
134
141
  .description('decrypt .env file(s)')
135
142
  .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
136
- .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
143
+ .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
137
144
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
138
145
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
139
146
  .option('--no-armor', 'disable Dotenvx Armor features')
@@ -149,8 +156,8 @@ program.command('keypair')
149
156
  .usage('[KEY] [options]')
150
157
  .description('print public/private keys for .env file(s)')
151
158
  .argument('[KEY]', 'environment variable key name')
152
- .option('-f, --env-file <path>', 'path(s) to your env file(s)')
153
- .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
159
+ .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'))
160
+ .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
154
161
  .option('--no-armor', 'disable Dotenvx Armor features')
155
162
  .option('--no-native', 'disable OS secret store features')
156
163
  .option('-pp, --pretty-print', 'pretty print output')
@@ -0,0 +1,40 @@
1
+ const conventions = require('./conventions')
2
+ const resolveDirectoryFilepath = require('./resolveDirectoryFilepath')
3
+
4
+ function buildCommandEnvs (envs, convention) {
5
+ const resolvedEnvs = []
6
+ let hasDirectory = false
7
+
8
+ for (const env of envs) {
9
+ if (env.type !== 'envFile') {
10
+ resolvedEnvs.push(env)
11
+ continue
12
+ }
13
+
14
+ const envFilepath = resolveDirectoryFilepath(env.value, '.env')
15
+ if (envFilepath === env.value) {
16
+ resolvedEnvs.push(env)
17
+ continue
18
+ }
19
+
20
+ hasDirectory = true
21
+ if (convention) {
22
+ for (const conventionEnv of conventions(convention)) {
23
+ resolvedEnvs.push({
24
+ ...conventionEnv,
25
+ value: resolveDirectoryFilepath(env.value, conventionEnv.value)
26
+ })
27
+ }
28
+ } else {
29
+ resolvedEnvs.push({ ...env, value: envFilepath })
30
+ }
31
+ }
32
+
33
+ if (convention && !hasDirectory) {
34
+ return conventions(convention).concat(resolvedEnvs)
35
+ }
36
+
37
+ return resolvedEnvs
38
+ }
39
+
40
+ module.exports = buildCommandEnvs
@@ -0,0 +1,16 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ function resolveDirectoryFilepath (filepath, filename) {
5
+ try {
6
+ if (fs.statSync(filepath).isDirectory()) {
7
+ return path.join(filepath, filename)
8
+ }
9
+ } catch (_error) {
10
+ // Preserve missing paths so the existing error handling can report them.
11
+ }
12
+
13
+ return filepath
14
+ }
15
+
16
+ module.exports = resolveDirectoryFilepath
@@ -0,0 +1,11 @@
1
+ const resolveDirectoryFilepath = require('./resolveDirectoryFilepath')
2
+
3
+ function resolveEnvKeysFile (envKeysFile) {
4
+ if (Array.isArray(envKeysFile)) {
5
+ return envKeysFile.map(filepath => resolveDirectoryFilepath(filepath, '.env.keys'))
6
+ }
7
+
8
+ return envKeysFile && resolveDirectoryFilepath(envKeysFile, '.env.keys')
9
+ }
10
+
11
+ module.exports = resolveEnvKeysFile