@dotenvx/dotenvx 1.29.0 → 1.30.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,30 @@
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/v1.29.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.30.0...main)
6
+
7
+ ## [1.30.0](https://github.com/dotenvx/dotenvx/compare/v1.29.0...v1.30.0)
8
+
9
+ ### Added
10
+
11
+ * add `-fk` (`--env-keys-file`) flag to customize the path to your `.env.keys` file with `run, get, set, encrypt, decrypt, and keypair` 🎉 ([#486](https://github.com/dotenvx/dotenvx/pull/486))
12
+
13
+ This is great for monorepos. Maintain one `.env.keys` file across multiple monorepos `.env*` files.
14
+
15
+ ```sh
16
+ $ dotenvx encrypt -fk .env.keys -f apps/backend/.env
17
+ $ dotenvx encrypt -fk .env.keys -f apps/frontend/.env
18
+
19
+ $ tree -a .
20
+ ├── .env.keys
21
+ └── apps
22
+ ├── backend
23
+ │   └── .env
24
+ └── frontend
25
+ └── .env
26
+
27
+ $ dotenvx get -fk .env.keys -f apps/backend/.env
28
+ ```
6
29
 
7
30
  ## [1.29.0](https://github.com/dotenvx/dotenvx/compare/v1.28.0...v1.29.0)
8
31
 
package/README.md CHANGED
@@ -583,7 +583,7 @@ More examples
583
583
  </details>
584
584
  * <details><summary>`--log-level` flag</summary><br>
585
585
 
586
- Set `--log-level` to whatever you wish. For example, to supress warnings (risky), set log level to `error`:
586
+ Set `--log-level` to whatever you wish. For example, to suppress warnings (risky), set log level to `error`:
587
587
 
588
588
  ```sh
589
589
  $ echo "HELLO=production" > .env.production
@@ -956,7 +956,7 @@ More examples
956
956
  </details>
957
957
  * <details><summary>`run --log-level`</summary><br>
958
958
 
959
- Set `--log-level` to whatever you wish. For example, to supress warnings (risky), set log level to `error`:
959
+ Set `--log-level` to whatever you wish. For example, to suppress warnings (risky), set log level to `error`:
960
960
 
961
961
  ```sh
962
962
  $ echo "HELLO=production" > .env.production
@@ -1014,6 +1014,19 @@ More examples
1014
1014
 
1015
1015
  (more conventions available upon request)
1016
1016
 
1017
+ </details>
1018
+ * <details><summary>`run -fk`</summary><br>
1019
+
1020
+ Specify path to `.env.keys`. This is useful with monorepos.
1021
+
1022
+ ```sh
1023
+ $ mkdir -p apps/app1
1024
+ $ touch apps/app1/.env
1025
+ $ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
1026
+
1027
+ $ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
1028
+ ```
1029
+
1017
1030
  </details>
1018
1031
  * <details><summary>`get KEY`</summary><br>
1019
1032
 
@@ -1039,6 +1052,20 @@ More examples
1039
1052
  production
1040
1053
  ```
1041
1054
 
1055
+ </details>
1056
+ * <details><summary>`get KEY -fk`</summary><br>
1057
+
1058
+ Specify path to `.env.keys`. This is useful with monorepos.
1059
+
1060
+ ```sh
1061
+ $ mkdir -p apps/app1
1062
+ $ touch apps/app1/.env
1063
+ $ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
1064
+
1065
+ $ dotenvx get HELLO -fk .env.keys -f apps/app1/.env
1066
+ world
1067
+ ```
1068
+
1042
1069
  </details>
1043
1070
  * <details><summary>`get KEY --env`</summary><br>
1044
1071
 
@@ -1212,6 +1239,32 @@ More examples
1212
1239
  set HELLO with encryption (.env.production)
1213
1240
  ```
1214
1241
 
1242
+ </details>
1243
+ * <details><summary>`set KEY value -fk`</summary><br>
1244
+
1245
+ Specify path to `.env.keys`. This is useful with monorepos.
1246
+
1247
+ ```sh
1248
+ $ mkdir -p apps/app1
1249
+ $ touch apps/app1/.env
1250
+
1251
+ $ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
1252
+ set HELLO with encryption (.env)
1253
+ ```
1254
+
1255
+ Put it to use.
1256
+
1257
+ ```sh
1258
+ $ dotenvx get -fk .env.keys -f apps/app1/.env
1259
+ ```
1260
+
1261
+ Use it with a relative path.
1262
+
1263
+ ```sh
1264
+ $ cd apps/app1
1265
+ $ dotenvx get -fk ../../.env.keys -f .env
1266
+ ```
1267
+
1215
1268
  </details>
1216
1269
  * <details><summary>`set KEY "value with spaces"`</summary><br>
1217
1270
 
@@ -1279,6 +1332,32 @@ More examples
1279
1332
  ⮕ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locally
1280
1333
  ```
1281
1334
 
1335
+ </details>
1336
+ * <details><summary>`encrypt -fk`</summary><br>
1337
+
1338
+ Specify path to `.env.keys`. This is useful with monorepos.
1339
+
1340
+ ```sh
1341
+ $ mkdir -p apps/app1
1342
+ $ echo "HELLO=World" > apps/app1/.env
1343
+
1344
+ $ dotenvx encrypt -fk .env.keys -f apps/app1/.env
1345
+ ✔ encrypted (apps/app1/.env)
1346
+ ```
1347
+
1348
+ Put it to use.
1349
+
1350
+ ```sh
1351
+ $ dotenvx run -fk .env.keys -f apps/app1/.env
1352
+ ```
1353
+
1354
+ Use with a relative path.
1355
+
1356
+ ```sh
1357
+ $ cd apps/app1
1358
+ $ dotenvx run -fk ../../.env.keys -f .env
1359
+ ```
1360
+
1282
1361
  </details>
1283
1362
  * <details><summary>`encrypt -k`</summary><br>
1284
1363
 
@@ -1373,6 +1452,21 @@ More examples
1373
1452
  ✔ decrypted (.env.production)
1374
1453
  ```
1375
1454
 
1455
+ </details>
1456
+ * <details><summary>`decrypt -fk`</summary><br>
1457
+
1458
+ Specify path to `.env.keys`. This is useful with monorepos.
1459
+
1460
+ ```sh
1461
+ $ mkdir -p apps/app1
1462
+ $ echo "HELLO=World" > apps/app1/.env
1463
+
1464
+ $ dotenvx encrypt -fk .env.keys -f apps/app1/.env
1465
+ ✔ encrypted (apps/app1/.env)
1466
+ $ dotenvx decrypt -fk .env.keys -f apps/app1/.env
1467
+ ✔ decrypted (apps/app1/.env)
1468
+ ```
1469
+
1376
1470
  </details>
1377
1471
  * <details><summary>`decrypt -k`</summary><br>
1378
1472
 
@@ -1455,7 +1549,7 @@ More examples
1455
1549
  ```
1456
1550
 
1457
1551
  </details>
1458
- * <details><summary>`keypair -f .env.production`</summary><br>
1552
+ * <details><summary>`keypair -f`</summary><br>
1459
1553
 
1460
1554
  Print public/private keys for `.env.production` file.
1461
1555
 
@@ -1467,6 +1561,20 @@ More examples
1467
1561
  {"DOTENV_PUBLIC_KEY_PRODUCTION":"<publicKey>","DOTENV_PRIVATE_KEY_PRODUCTION":"<privateKey>"}
1468
1562
  ```
1469
1563
 
1564
+ </details>
1565
+ * <details><summary>`keypair -fk`</summary><br>
1566
+
1567
+ Specify path to `.env.keys`. This is useful for printing public/private keys for monorepos.
1568
+
1569
+ ```sh
1570
+ $ mkdir -p apps/app1
1571
+ $ echo "HELLO=World" > apps/app1/.env
1572
+ $ dotenvx encrypt -fk .env.keys -f apps/app1/.env
1573
+
1574
+ $ dotenvx keypair -fk .env.keys -f apps/app1/.env
1575
+ {"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}
1576
+ ```
1577
+
1470
1578
  </details>
1471
1579
  * <details><summary>`keypair DOTENV_PRIVATE_KEY`</summary><br>
1472
1580
 
@@ -1904,6 +2012,21 @@ More examples
1904
2012
  Hello World
1905
2013
  ```
1906
2014
 
2015
+ </details>
2016
+ * <details><summary>`config(envKeysFile:)` - envKeysFile</summary><br>
2017
+
2018
+ Use `envKeysFile` to customize the path to your `.env.keys` file. This is useful with monorepos.
2019
+
2020
+ ```ini
2021
+ # .env
2022
+ HELLO="World"
2023
+ ```
2024
+
2025
+ ```js
2026
+ // index.js
2027
+ require('@dotenvx/dotenvx').config({envKeysFile: '../../.env.keys'})
2028
+ ```
2029
+
1907
2030
  </details>
1908
2031
  * <details><summary>`parse(src)`</summary><br>
1909
2032
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.29.0",
2
+ "version": "1.30.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -17,12 +17,15 @@ function decrypt () {
17
17
  if (options.stdout) {
18
18
  const {
19
19
  processedEnvs
20
- } = new Decrypt(envs, options.key, options.excludeKey).run()
20
+ } = new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile).run()
21
21
 
22
22
  for (const processedEnv of processedEnvs) {
23
23
  if (processedEnv.error) {
24
24
  errorCount += 1
25
25
  console.error(processedEnv.error.message)
26
+ if (processedEnv.error.help) {
27
+ console.error(processedEnv.error.help)
28
+ }
26
29
  } else {
27
30
  console.log(processedEnv.envSrc)
28
31
  }
@@ -39,7 +42,7 @@ function decrypt () {
39
42
  processedEnvs,
40
43
  changedFilepaths,
41
44
  unchangedFilepaths
42
- } = new Decrypt(envs, options.key, options.excludeKey).run()
45
+ } = new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile).run()
43
46
 
44
47
  for (const processedEnv of processedEnvs) {
45
48
  logger.verbose(`decrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -52,6 +55,9 @@ function decrypt () {
52
55
  logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx decrypt]`)
53
56
  } else {
54
57
  console.error(processedEnv.error.message)
58
+ if (processedEnv.error.help) {
59
+ console.error(processedEnv.error.help)
60
+ }
55
61
  }
56
62
  } else if (processedEnv.changed) {
57
63
  fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
@@ -16,7 +16,7 @@ function encrypt () {
16
16
  if (options.stdout) {
17
17
  const {
18
18
  processedEnvs
19
- } = new Encrypt(envs, options.key, options.excludeKey).run()
19
+ } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile).run()
20
20
 
21
21
  for (const processedEnv of processedEnvs) {
22
22
  console.log(processedEnv.envSrc)
@@ -28,7 +28,7 @@ function encrypt () {
28
28
  processedEnvs,
29
29
  changedFilepaths,
30
30
  unchangedFilepaths
31
- } = new Encrypt(envs, options.key, options.excludeKey).run()
31
+ } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile).run()
32
32
 
33
33
  for (const processedEnv of processedEnvs) {
34
34
  logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -24,7 +24,7 @@ function get (key) {
24
24
  }
25
25
 
26
26
  try {
27
- const { parsed, errors } = new Get(key, envs, options.overload, process.env.DOTENV_KEY, options.all).run()
27
+ const { parsed, errors } = new Get(key, envs, options.overload, process.env.DOTENV_KEY, options.all, options.envKeysFile).run()
28
28
 
29
29
  for (const error of errors || []) {
30
30
  if (options.strict) throw error // throw immediately if strict
@@ -10,7 +10,7 @@ function keypair (key) {
10
10
  const options = this.opts()
11
11
  logger.debug(`options: ${JSON.stringify(options)}`)
12
12
 
13
- const results = main.keypair(options.envFile, key)
13
+ const results = main.keypair(options.envFile, key, options.envKeysFile)
14
14
 
15
15
  if (typeof results === 'object' && results !== null) {
16
16
  // inline shell format - env $(dotenvx keypair --format=shell) your-command
@@ -45,7 +45,7 @@ async function run () {
45
45
  readableStrings,
46
46
  readableFilepaths,
47
47
  uniqueInjectedKeys
48
- } = new Run(envs, options.overload, process.env.DOTENV_KEY).run()
48
+ } = new Run(envs, options.overload, process.env.DOTENV_KEY, process.env, options.envKeysFile).run()
49
49
 
50
50
  for (const processedEnv of processedEnvs) {
51
51
  if (processedEnv.type === 'envVaultFile') {
@@ -21,12 +21,13 @@ function set (key, value) {
21
21
 
22
22
  try {
23
23
  const envs = this.envs
24
+ const envKeysFilepath = options.envKeysFile
24
25
 
25
26
  const {
26
27
  processedEnvs,
27
28
  changedFilepaths,
28
29
  unchangedFilepaths
29
- } = new Sets(key, value, envs, encrypt).run()
30
+ } = new Sets(key, value, envs, encrypt, envKeysFilepath).run()
30
31
 
31
32
  let withEncryption = ''
32
33
 
@@ -65,7 +66,7 @@ function set (key, value) {
65
66
 
66
67
  for (const processedEnv of processedEnvs) {
67
68
  if (processedEnv.privateKeyAdded) {
68
- logger.success(`✔ key added to .env.keys (${processedEnv.privateKeyName})`)
69
+ logger.success(`✔ key added to ${processedEnv.envKeysFilepath} (${processedEnv.privateKeyName})`)
69
70
 
70
71
  if (!isIgnoringDotenvKeys()) {
71
72
  logger.help('⮕ next run [dotenvx ext gitignore --pattern .env.keys] to gitignore .env.keys')
@@ -56,6 +56,7 @@ program.command('run')
56
56
  .addHelpText('after', examples.run)
57
57
  .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
58
58
  .option('-f, --env-file <paths...>', '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)')
59
60
  .option('-fv, --env-vault-file <paths...>', 'path(s) to your .env.vault file(s)', collectEnvs('envVaultFile'), [])
60
61
  .option('-o, --overload', 'override existing env variables')
61
62
  .option('--strict', 'process.exit(1) on any errors (default: false)', false)
@@ -74,6 +75,7 @@ program.command('get')
74
75
  .argument('[KEY]', 'environment variable name')
75
76
  .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
76
77
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
78
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
77
79
  .option('-fv, --env-vault-file <paths...>', 'path(s) to your .env.vault file(s)', collectEnvs('envVaultFile'), [])
78
80
  .option('-o, --overload', 'override existing env variables')
79
81
  .option('--strict', 'process.exit(1) on any errors (default: false)', false)
@@ -97,6 +99,7 @@ program.command('set')
97
99
  .argument('KEY', 'KEY')
98
100
  .argument('value', 'value')
99
101
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
102
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
100
103
  .option('-c, --encrypt', 'encrypt value (default: true)', true)
101
104
  .option('-p, --plain', 'store value as plain text', false)
102
105
  .action(function (...args) {
@@ -109,6 +112,7 @@ const encryptAction = require('./actions/encrypt')
109
112
  program.command('encrypt')
110
113
  .description('convert .env file(s) to encrypted .env file(s)')
111
114
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
115
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
112
116
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
113
117
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
114
118
  .option('--stdout', 'send to stdout')
@@ -122,6 +126,7 @@ const decryptAction = require('./actions/decrypt')
122
126
  program.command('decrypt')
123
127
  .description('convert encrypted .env file(s) to plain .env file(s)')
124
128
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
129
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
125
130
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
126
131
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
127
132
  .option('--stdout', 'send to stdout')
@@ -137,6 +142,7 @@ program.command('keypair')
137
142
  .description('print public/private keys for .env file(s)')
138
143
  .argument('[KEY]', 'environment variable key name')
139
144
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
145
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
140
146
  .option('-pp, --pretty-print', 'pretty print output')
141
147
  .option('--format <type>', 'format of the output (json, shell)', 'json')
142
148
  .action(keypairAction)
@@ -1,33 +1,18 @@
1
- const path = require('path')
2
- const childProcess = require('child_process')
3
-
4
1
  // helpers
5
2
  const guessPrivateKeyName = require('./guessPrivateKeyName')
3
+ const ProKeypair = require('./proKeypair')
6
4
 
7
5
  // services
8
6
  const Keypair = require('./../services/keypair')
9
7
 
10
- function findPrivateKey (envFilepath) {
8
+ function findPrivateKey (envFilepath, envKeysFilepath = null) {
9
+ // use path/to/.env.${environment} to generate privateKeyName
11
10
  const privateKeyName = guessPrivateKeyName(envFilepath)
12
11
 
13
- let privateKey
14
- try {
15
- // if installed as sibling module
16
- const projectRoot = path.resolve(process.cwd())
17
- const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] })
18
- const { keypair } = require(dotenvxProPath)
19
- privateKey = keypair(envFilepath, privateKeyName)
20
- } catch (_e) {
21
- try {
22
- // if installed as binary cli
23
- privateKey = childProcess.execSync(`dotenvx-pro keypair ${privateKeyName} -f ${envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
24
- } catch (_e) {
25
- // fallback to local KeyPair - smart enough to handle process.env, .env.keys, etc
26
- privateKey = new Keypair(envFilepath, privateKeyName).run()
27
- }
28
- }
12
+ const proKeypairs = new ProKeypair(envFilepath).run() // TODO: implement custom envKeysFilepath
13
+ const keypairs = new Keypair(envFilepath, envKeysFilepath).run()
29
14
 
30
- return privateKey
15
+ return proKeypairs[privateKeyName] || keypairs[privateKeyName]
31
16
  }
32
17
 
33
18
  module.exports = findPrivateKey
@@ -1,8 +1,6 @@
1
- const path = require('path')
2
- const childProcess = require('child_process')
3
-
4
1
  // helpers
5
2
  const guessPublicKeyName = require('./guessPublicKeyName')
3
+ const ProKeypair = require('./proKeypair')
6
4
 
7
5
  // services
8
6
  const Keypair = require('./../services/keypair')
@@ -10,24 +8,10 @@ const Keypair = require('./../services/keypair')
10
8
  function findPublicKey (envFilepath) {
11
9
  const publicKeyName = guessPublicKeyName(envFilepath)
12
10
 
13
- let publicKey
14
- try {
15
- // if installed as sibling module
16
- const projectRoot = path.resolve(process.cwd())
17
- const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] })
18
- const { keypair } = require(dotenvxProPath)
19
- publicKey = keypair(envFilepath, publicKeyName)
20
- } catch (_e) {
21
- try {
22
- // if installed as binary cli
23
- publicKey = childProcess.execSync(`dotenvx-pro keypair ${publicKeyName} -f ${envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
24
- } catch (_e) {
25
- // fallback to local KeyPair - smart enough to handle process.env, .env.keys, etc
26
- publicKey = new Keypair(envFilepath, publicKeyName).run()
27
- }
28
- }
11
+ const proKeypairs = new ProKeypair(envFilepath).run()
12
+ const keypairs = new Keypair(envFilepath).run()
29
13
 
30
- return publicKey
14
+ return proKeypairs[publicKeyName] || keypairs[publicKeyName]
31
15
  }
32
16
 
33
17
  module.exports = findPublicKey
@@ -1,6 +1,6 @@
1
1
  const { PrivateKey } = require('eciesjs')
2
2
 
3
- function keyPair (existingPrivateKey) {
3
+ function keypair (existingPrivateKey) {
4
4
  let kp
5
5
 
6
6
  if (existingPrivateKey) {
@@ -18,4 +18,4 @@ function keyPair (existingPrivateKey) {
18
18
  }
19
19
  }
20
20
 
21
- module.exports = keyPair
21
+ module.exports = keypair
@@ -0,0 +1,42 @@
1
+ const path = require('path')
2
+ const childProcess = require('child_process')
3
+
4
+ const guessPrivateKeyName = require('./guessPrivateKeyName')
5
+ const guessPublicKeyName = require('./guessPublicKeyName')
6
+
7
+ class ProKeypair {
8
+ constructor (envFilepath) {
9
+ this.envFilepath = envFilepath
10
+ }
11
+
12
+ run () {
13
+ let result = {}
14
+
15
+ try {
16
+ // if installed as sibling module
17
+ const projectRoot = path.resolve(process.cwd())
18
+ const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] })
19
+ const { keypair } = require(dotenvxProPath)
20
+
21
+ result = keypair(this.envFilepath)
22
+ } catch (_e) {
23
+ try {
24
+ // if installed as binary cli
25
+ const output = childProcess.execSync(`dotenvx-pro keypair -f ${this.envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
26
+
27
+ result = JSON.parse(output)
28
+ } catch (_e) {
29
+ const privateKeyName = guessPrivateKeyName(this.envFilepath)
30
+ const publicKeyName = guessPublicKeyName(this.envFilepath)
31
+
32
+ // match format of dotenvx-pro
33
+ result[privateKeyName] = null
34
+ result[publicKeyName] = null
35
+ }
36
+ }
37
+
38
+ return result
39
+ }
40
+ }
41
+
42
+ module.exports = ProKeypair
@@ -13,12 +13,14 @@ function searchProcessEnv (privateKeyName) {
13
13
  }
14
14
  }
15
15
 
16
- function searchKeysFile (privateKeyName, envFilepath) {
17
- const directory = path.dirname(envFilepath)
18
- const envKeysFilepath = path.resolve(directory, '.env.keys')
16
+ function searchKeysFile (privateKeyName, envFilepath, envKeysFilepath = null) {
17
+ let keysFilepath = path.resolve(path.dirname(envFilepath), '.env.keys') // typical scenario
18
+ if (envKeysFilepath) { // user specified -fk flag
19
+ keysFilepath = path.resolve(envKeysFilepath)
20
+ }
19
21
 
20
- if (fsx.existsSync(envKeysFilepath)) {
21
- const keysSrc = fsx.readFileX(envKeysFilepath)
22
+ if (fsx.existsSync(keysFilepath)) {
23
+ const keysSrc = fsx.readFileX(keysFilepath)
22
24
  const keysParsed = dotenv.parse(keysSrc)
23
25
 
24
26
  if (keysParsed[privateKeyName] && keysParsed[privateKeyName].length > 0) {
@@ -49,7 +51,7 @@ function invertForPrivateKeyName (envFilepath) {
49
51
  return null
50
52
  }
51
53
 
52
- function smartDotenvPrivateKey (envFilepath) {
54
+ function smartDotenvPrivateKey (envFilepath, envKeysFilepath = null) {
53
55
  let privateKey = null
54
56
  let privateKeyName = guessPrivateKeyName(envFilepath) // DOTENV_PRIVATE_KEY_${ENVIRONMENT}
55
57
 
@@ -60,7 +62,7 @@ function smartDotenvPrivateKey (envFilepath) {
60
62
  }
61
63
 
62
64
  // 2. attempt .env.keys second (path/to/.env.keys)
63
- privateKey = searchKeysFile(privateKeyName, envFilepath)
65
+ privateKey = searchKeysFile(privateKeyName, envFilepath, envKeysFilepath)
64
66
  if (privateKey) {
65
67
  return privateKey
66
68
  }
@@ -75,7 +77,7 @@ function smartDotenvPrivateKey (envFilepath) {
75
77
  }
76
78
 
77
79
  // 3.2. attempt .env.keys second (path/to/.env.keys)
78
- privateKey = searchKeysFile(privateKeyName, envFilepath)
80
+ privateKey = searchKeysFile(privateKeyName, envFilepath, envKeysFilepath)
79
81
  if (privateKey) {
80
82
  return privateKey
81
83
  }
package/src/lib/main.js CHANGED
@@ -34,6 +34,9 @@ const config = function (options = {}) {
34
34
  // strict
35
35
  const strict = options.strict
36
36
 
37
+ // envKeysFile
38
+ const envKeysFile = options.envKeysFile
39
+
37
40
  // DOTENV_KEY (DEPRECATED)
38
41
  let DOTENV_KEY = process.env.DOTENV_KEY
39
42
  if (options && options.DOTENV_KEY) {
@@ -70,7 +73,7 @@ const config = function (options = {}) {
70
73
  processedEnvs,
71
74
  readableFilepaths,
72
75
  uniqueInjectedKeys
73
- } = new Run(envs, overload, DOTENV_KEY, processEnv).run()
76
+ } = new Run(envs, overload, DOTENV_KEY, processEnv, envKeysFile).run()
74
77
 
75
78
  let lastError
76
79
  /** @type {Record<string, string>} */
@@ -190,8 +193,13 @@ const genexample = function (directory, envFile) {
190
193
  }
191
194
 
192
195
  /** @type {import('./main').keypair} */
193
- const keypair = function (envFile, key) {
194
- return new Keypair(envFile, key).run()
196
+ const keypair = function (envFile, key, envKeysFile = null) {
197
+ const keypairs = new Keypair(envFile, envKeysFile).run()
198
+ if (key) {
199
+ return keypairs[key]
200
+ } else {
201
+ return keypairs
202
+ }
195
203
  }
196
204
 
197
205
  module.exports = {
@@ -15,10 +15,11 @@ const detectEncoding = require('./../helpers/detectEncoding')
15
15
  const determineEnvs = require('./../helpers/determineEnvs')
16
16
 
17
17
  class Decrypt {
18
- constructor (envs = [], key = [], excludeKey = []) {
18
+ constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null) {
19
19
  this.envs = determineEnvs(envs, process.env)
20
20
  this.key = key
21
21
  this.excludeKey = excludeKey
22
+ this.envKeysFilepath = envKeysFilepath
22
23
 
23
24
  this.processedEnvs = []
24
25
  this.changedFilepaths = new Set()
@@ -64,7 +65,7 @@ class Decrypt {
64
65
  let envSrc = fsx.readFileX(filepath, { encoding })
65
66
  const envParsed = dotenv.parse(envSrc)
66
67
 
67
- const privateKey = findPrivateKey(envFilepath)
68
+ const privateKey = findPrivateKey(envFilepath, this.envKeysFilepath)
68
69
  const privateKeyName = guessPrivateKeyName(envFilepath)
69
70
 
70
71
  row.privateKey = privateKey
@@ -15,15 +15,16 @@ const detectEncoding = require('./../helpers/detectEncoding')
15
15
  const determineEnvs = require('./../helpers/determineEnvs')
16
16
  const findPrivateKey = require('./../helpers/findPrivateKey')
17
17
  const findPublicKey = require('./../helpers/findPublicKey')
18
- const keyPair = require('./../helpers/keyPair')
18
+ const keypair = require('./../helpers/keypair')
19
19
  const truncate = require('./../helpers/truncate')
20
20
  const isPublicKey = require('./../helpers/isPublicKey')
21
21
 
22
22
  class Encrypt {
23
- constructor (envs = [], key = [], excludeKey = []) {
23
+ constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null) {
24
24
  this.envs = determineEnvs(envs, process.env)
25
25
  this.key = key
26
26
  this.excludeKey = excludeKey
27
+ this.envKeysFilepath = envKeysFilepath
27
28
 
28
29
  this.processedEnvs = []
29
30
  this.changedFilepaths = new Set()
@@ -75,11 +76,17 @@ class Encrypt {
75
76
 
76
77
  const publicKeyName = guessPublicKeyName(envFilepath)
77
78
  const privateKeyName = guessPrivateKeyName(envFilepath)
78
- const existingPrivateKey = findPrivateKey(envFilepath)
79
+ const existingPrivateKey = findPrivateKey(envFilepath, this.envKeysFilepath)
79
80
  const existingPublicKey = findPublicKey(envFilepath)
80
81
 
82
+ let envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
83
+ if (this.envKeysFilepath) {
84
+ envKeysFilepath = path.resolve(this.envKeysFilepath)
85
+ }
86
+ const relativeFilepath = path.relative(path.dirname(filepath), envKeysFilepath)
87
+
81
88
  if (existingPrivateKey) {
82
- const kp = keyPair(existingPrivateKey)
89
+ const kp = keypair(existingPrivateKey)
83
90
  publicKey = kp.publicKey
84
91
  privateKey = kp.privateKey
85
92
 
@@ -90,38 +97,36 @@ class Encrypt {
90
97
  error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
91
98
  throw error
92
99
  }
100
+
101
+ // typical scenario when encrypting a monorepo second .env file from a prior generated -fk .env.keys file
102
+ if (!existingPublicKey) {
103
+ const ps = this._preserveShebang(envSrc)
104
+ const firstLinePreserved = ps.firstLinePreserved
105
+ envSrc = ps.envSrc
106
+
107
+ const prependPublicKey = this._prependPublicKey(publicKeyName, publicKey, filename, relativeFilepath)
108
+
109
+ envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
110
+ }
93
111
  } else if (existingPublicKey) {
94
112
  publicKey = existingPublicKey
95
113
  } else {
96
114
  // .env.keys
97
115
  let keysSrc = ''
98
- const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
116
+
99
117
  if (fsx.existsSync(envKeysFilepath)) {
100
118
  keysSrc = fsx.readFileX(envKeysFilepath)
101
119
  }
102
120
 
103
- // preserve shebang
104
- const [firstLine, ...remainingLines] = envSrc.split('\n')
105
- let firstLinePreserved = ''
106
- if (firstLine.startsWith('#!')) {
107
- firstLinePreserved = firstLine + '\n'
108
- envSrc = remainingLines.join('\n')
109
- }
121
+ const ps = this._preserveShebang(envSrc)
122
+ const firstLinePreserved = ps.firstLinePreserved
123
+ envSrc = ps.envSrc
110
124
 
111
- const kp = keyPair() // generates a fresh keypair in memory
125
+ const kp = keypair() // generates a fresh keypair in memory
112
126
  publicKey = kp.publicKey
113
127
  privateKey = kp.privateKey
114
128
 
115
- // publicKey
116
- const prependPublicKey = [
117
- '#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
118
- '#/ public-key encryption for .env files /',
119
- '#/ [how it works](https://dotenvx.com/encryption) /',
120
- '#/----------------------------------------------------------/',
121
- `${publicKeyName}="${publicKey}"`,
122
- '',
123
- `# ${filename}`
124
- ].join('\n')
129
+ const prependPublicKey = this._prependPublicKey(publicKeyName, publicKey, filename, relativeFilepath)
125
130
 
126
131
  // privateKey
127
132
  const firstTimeKeysSrc = [
@@ -144,6 +149,7 @@ class Encrypt {
144
149
  fsx.writeFileX(envKeysFilepath, keysSrc)
145
150
 
146
151
  row.privateKeyAdded = true
152
+ row.envKeysFilepath = this.envKeysFilepath || path.join(path.dirname(envFilepath), path.basename(envKeysFilepath))
147
153
  }
148
154
 
149
155
  row.publicKey = publicKey
@@ -210,6 +216,36 @@ class Encrypt {
210
216
  _detectEncoding (filepath) {
211
217
  return detectEncoding(filepath)
212
218
  }
219
+
220
+ _prependPublicKey (publicKeyName, publicKey, filename, relativeFilepath = '') {
221
+ const comment = relativeFilepath === '.env.keys' ? '' : ` # ${relativeFilepath}`
222
+
223
+ return [
224
+ '#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
225
+ '#/ public-key encryption for .env files /',
226
+ '#/ [how it works](https://dotenvx.com/encryption) /',
227
+ '#/----------------------------------------------------------/',
228
+ `${publicKeyName}="${publicKey}"${comment}`,
229
+ '',
230
+ `# ${filename}`
231
+ ].join('\n')
232
+ }
233
+
234
+ _preserveShebang (envSrc) {
235
+ // preserve shebang
236
+ const [firstLine, ...remainingLines] = envSrc.split('\n')
237
+ let firstLinePreserved = ''
238
+
239
+ if (firstLine.startsWith('#!')) {
240
+ firstLinePreserved = firstLine + '\n'
241
+ envSrc = remainingLines.join('\n')
242
+ }
243
+
244
+ return {
245
+ firstLinePreserved,
246
+ envSrc
247
+ }
248
+ }
213
249
  }
214
250
 
215
251
  module.exports = Encrypt
@@ -2,18 +2,18 @@ const Run = require('./run')
2
2
  const Errors = require('./../helpers/errors')
3
3
 
4
4
  class Get {
5
- constructor (key, envs = [], overload = false, DOTENV_KEY = '', all = false, strict = false) {
5
+ constructor (key, envs = [], overload = false, DOTENV_KEY = '', all = false, envKeysFilepath = null) {
6
6
  this.key = key
7
7
  this.envs = envs
8
8
  this.overload = overload
9
9
  this.DOTENV_KEY = DOTENV_KEY
10
10
  this.all = all
11
- this.strict = strict
11
+ this.envKeysFilepath = envKeysFilepath
12
12
  }
13
13
 
14
14
  run () {
15
15
  const processEnv = { ...process.env }
16
- const { processedEnvs } = new Run(this.envs, this.overload, this.DOTENV_KEY, processEnv).run()
16
+ const { processedEnvs } = new Run(this.envs, this.overload, this.DOTENV_KEY, processEnv, this.envKeysFilepath).run()
17
17
 
18
18
  const errors = []
19
19
  for (const processedEnv of processedEnvs) {
@@ -4,9 +4,9 @@ const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
4
4
  const smartDotenvPrivateKey = require('./../helpers/smartDotenvPrivateKey')
5
5
 
6
6
  class Keypair {
7
- constructor (envFile = '.env', key = undefined) {
7
+ constructor (envFile = '.env', envKeysFilepath = null) {
8
8
  this.envFile = envFile
9
- this.key = key
9
+ this.envKeysFilepath = envKeysFilepath
10
10
  }
11
11
 
12
12
  run () {
@@ -21,16 +21,12 @@ class Keypair {
21
21
 
22
22
  // private key
23
23
  const privateKeyName = guessPrivateKeyName(envFilepath)
24
- const privateKeyValue = smartDotenvPrivateKey(envFilepath)
24
+ const privateKeyValue = smartDotenvPrivateKey(envFilepath, this.envKeysFilepath)
25
25
 
26
26
  out[privateKeyName] = privateKeyValue
27
27
  }
28
28
 
29
- if (this.key) {
30
- return out[this.key]
31
- } else {
32
- return out
33
- }
29
+ return out
34
30
  }
35
31
 
36
32
  _envFilepaths () {
@@ -16,11 +16,12 @@ const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
16
16
  const determineEnvs = require('./../helpers/determineEnvs')
17
17
 
18
18
  class Run {
19
- constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env) {
19
+ constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env, envKeysFilepath = null) {
20
20
  this.envs = determineEnvs(envs, processEnv, DOTENV_KEY)
21
21
  this.overload = overload
22
22
  this.DOTENV_KEY = DOTENV_KEY
23
23
  this.processEnv = processEnv
24
+ this.envKeysFilepath = envKeysFilepath
24
25
 
25
26
  this.processedEnvs = []
26
27
  this.readableFilepaths = new Set()
@@ -92,7 +93,7 @@ class Run {
92
93
  const src = fsx.readFileX(filepath, { encoding })
93
94
  this.readableFilepaths.add(envFilepath)
94
95
 
95
- const privateKey = findPrivateKey(envFilepath)
96
+ const privateKey = findPrivateKey(envFilepath, this.envKeysFilepath)
96
97
  const privateKeyName = guessPrivateKeyName(envFilepath)
97
98
  const { parsed, errors, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload, privateKeyName).run()
98
99
 
@@ -14,16 +14,17 @@ const detectEncoding = require('./../helpers/detectEncoding')
14
14
  const determineEnvs = require('./../helpers/determineEnvs')
15
15
  const findPrivateKey = require('./../helpers/findPrivateKey')
16
16
  const findPublicKey = require('./../helpers/findPublicKey')
17
- const keyPair = require('./../helpers/keyPair')
17
+ const keypair = require('./../helpers/keypair')
18
18
  const truncate = require('./../helpers/truncate')
19
19
  const isEncrypted = require('./../helpers/isEncrypted')
20
20
 
21
21
  class Sets {
22
- constructor (key, value, envs = [], encrypt = true) {
22
+ constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null) {
23
23
  this.envs = determineEnvs(envs, process.env)
24
24
  this.key = key
25
25
  this.value = value
26
26
  this.encrypt = encrypt
27
+ this.envKeysFilepath = envKeysFilepath
27
28
 
28
29
  this.processedEnvs = []
29
30
  this.changedFilepaths = new Set()
@@ -76,11 +77,17 @@ class Sets {
76
77
 
77
78
  const publicKeyName = guessPublicKeyName(envFilepath)
78
79
  const privateKeyName = guessPrivateKeyName(envFilepath)
79
- const existingPrivateKey = findPrivateKey(envFilepath)
80
+ const existingPrivateKey = findPrivateKey(envFilepath, this.envKeysFilepath)
80
81
  const existingPublicKey = findPublicKey(envFilepath)
81
82
 
83
+ let envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
84
+ if (this.envKeysFilepath) {
85
+ envKeysFilepath = path.resolve(this.envKeysFilepath)
86
+ }
87
+ const relativeFilepath = path.relative(path.dirname(filepath), envKeysFilepath)
88
+
82
89
  if (existingPrivateKey) {
83
- const kp = keyPair(existingPrivateKey)
90
+ const kp = keypair(existingPrivateKey)
84
91
  publicKey = kp.publicKey
85
92
  privateKey = kp.privateKey
86
93
 
@@ -95,38 +102,35 @@ class Sets {
95
102
  error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
96
103
  throw error
97
104
  }
105
+
106
+ // typical scenario when encrypting a monorepo second .env file from a prior generated -fk .env.keys file
107
+ if (!existingPublicKey) {
108
+ const ps = this._preserveShebang(envSrc)
109
+ const firstLinePreserved = ps.firstLinePreserved
110
+ envSrc = ps.envSrc
111
+
112
+ const prependPublicKey = this._prependPublicKey(publicKeyName, publicKey, filename, relativeFilepath)
113
+
114
+ envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
115
+ }
98
116
  } else if (existingPublicKey) {
99
117
  publicKey = existingPublicKey
100
118
  } else {
101
119
  // .env.keys
102
120
  let keysSrc = ''
103
- const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
104
121
  if (fsx.existsSync(envKeysFilepath)) {
105
122
  keysSrc = fsx.readFileX(envKeysFilepath)
106
123
  }
107
124
 
108
- // preserve shebang
109
- const [firstLine, ...remainingLines] = envSrc.split('\n')
110
- let firstLinePreserved = ''
111
- if (firstLine.startsWith('#!')) {
112
- firstLinePreserved = firstLine + '\n'
113
- envSrc = remainingLines.join('\n')
114
- }
125
+ const ps = this._preserveShebang(envSrc)
126
+ const firstLinePreserved = ps.firstLinePreserved
127
+ envSrc = ps.envSrc
115
128
 
116
- const kp = keyPair() // generates a fresh keypair in memory
129
+ const kp = keypair() // generates a fresh keypair in memory
117
130
  publicKey = kp.publicKey
118
131
  privateKey = kp.privateKey
119
132
 
120
- // publicKey
121
- const prependPublicKey = [
122
- '#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
123
- '#/ public-key encryption for .env files /',
124
- '#/ [how it works](https://dotenvx.com/encryption) /',
125
- '#/----------------------------------------------------------/',
126
- `${publicKeyName}="${publicKey}"`,
127
- '',
128
- `# ${filename}`
129
- ].join('\n')
133
+ const prependPublicKey = this._prependPublicKey(publicKeyName, publicKey, filename, relativeFilepath)
130
134
 
131
135
  // privateKey
132
136
  const firstTimeKeysSrc = [
@@ -149,6 +153,7 @@ class Sets {
149
153
  fsx.writeFileX(envKeysFilepath, keysSrc)
150
154
 
151
155
  row.privateKeyAdded = true
156
+ row.envKeysFilepath = this.envKeysFilepath || path.join(path.dirname(envFilepath), path.basename(envKeysFilepath))
152
157
  }
153
158
 
154
159
  row.publicKey = publicKey
@@ -182,6 +187,36 @@ class Sets {
182
187
  _detectEncoding (filepath) {
183
188
  return detectEncoding(filepath)
184
189
  }
190
+
191
+ _prependPublicKey (publicKeyName, publicKey, filename, relativeFilepath = '.env.keys') {
192
+ const comment = relativeFilepath === '.env.keys' ? '' : ` # -fk ${relativeFilepath}`
193
+
194
+ return [
195
+ '#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
196
+ '#/ public-key encryption for .env files /',
197
+ '#/ [how it works](https://dotenvx.com/encryption) /',
198
+ '#/----------------------------------------------------------/',
199
+ `${publicKeyName}="${publicKey}"${comment}`,
200
+ '',
201
+ `# ${filename}`
202
+ ].join('\n')
203
+ }
204
+
205
+ _preserveShebang (envSrc) {
206
+ // preserve shebang
207
+ const [firstLine, ...remainingLines] = envSrc.split('\n')
208
+ let firstLinePreserved = ''
209
+
210
+ if (firstLine.startsWith('#!')) {
211
+ firstLinePreserved = firstLine + '\n'
212
+ envSrc = remainingLines.join('\n')
213
+ }
214
+
215
+ return {
216
+ firstLinePreserved,
217
+ envSrc
218
+ }
219
+ }
185
220
  }
186
221
 
187
222
  module.exports = Sets