@dotenvx/dotenvx 1.28.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,36 @@
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.28.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
+ ```
29
+
30
+ ## [1.29.0](https://github.com/dotenvx/dotenvx/compare/v1.28.0...v1.29.0)
31
+
32
+ ### Added
33
+
34
+ * add `--ignore` flag to suppress specified errors. example: `dotenvx run --ignore=MISSING_ENV_FILE` ([#485](https://github.com/dotenvx/dotenvx/pull/485))
6
35
 
7
36
  ## [1.28.0](https://github.com/dotenvx/dotenvx/compare/v1.27.0...v1.28.0)
8
37
 
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
@@ -983,6 +983,18 @@ More examples
983
983
 
984
984
  This can be useful in `ci` scripts where you want to fail the ci if your `.env` file could not be decrypted at runtime.
985
985
 
986
+ </details>
987
+ * <details><summary>`run --ignore`</summary><br>
988
+
989
+ Ignore errors like `MISSING_ENV_FILE`.
990
+
991
+ ```sh
992
+ $ echo "console.log('Hello ' + process.env.HELLO)" > index.js
993
+
994
+ $ dotenvx run -f .env.missing --ignore=MISSING_ENV_FILE -- node index.js
995
+ ...
996
+ ```
997
+
986
998
  </details>
987
999
  * <details><summary>`run --convention=nextjs`</summary><br>
988
1000
 
@@ -1002,6 +1014,19 @@ More examples
1002
1014
 
1003
1015
  (more conventions available upon request)
1004
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
+
1005
1030
  </details>
1006
1031
  * <details><summary>`get KEY`</summary><br>
1007
1032
 
@@ -1027,6 +1052,20 @@ More examples
1027
1052
  production
1028
1053
  ```
1029
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
+
1030
1069
  </details>
1031
1070
  * <details><summary>`get KEY --env`</summary><br>
1032
1071
 
@@ -1200,6 +1239,32 @@ More examples
1200
1239
  set HELLO with encryption (.env.production)
1201
1240
  ```
1202
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
+
1203
1268
  </details>
1204
1269
  * <details><summary>`set KEY "value with spaces"`</summary><br>
1205
1270
 
@@ -1267,6 +1332,32 @@ More examples
1267
1332
  ⮕ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locally
1268
1333
  ```
1269
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
+
1270
1361
  </details>
1271
1362
  * <details><summary>`encrypt -k`</summary><br>
1272
1363
 
@@ -1361,6 +1452,21 @@ More examples
1361
1452
  ✔ decrypted (.env.production)
1362
1453
  ```
1363
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
+
1364
1470
  </details>
1365
1471
  * <details><summary>`decrypt -k`</summary><br>
1366
1472
 
@@ -1443,7 +1549,7 @@ More examples
1443
1549
  ```
1444
1550
 
1445
1551
  </details>
1446
- * <details><summary>`keypair -f .env.production`</summary><br>
1552
+ * <details><summary>`keypair -f`</summary><br>
1447
1553
 
1448
1554
  Print public/private keys for `.env.production` file.
1449
1555
 
@@ -1455,6 +1561,20 @@ More examples
1455
1561
  {"DOTENV_PUBLIC_KEY_PRODUCTION":"<publicKey>","DOTENV_PRIVATE_KEY_PRODUCTION":"<privateKey>"}
1456
1562
  ```
1457
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
+
1458
1578
  </details>
1459
1579
  * <details><summary>`keypair DOTENV_PRIVATE_KEY`</summary><br>
1460
1580
 
@@ -1695,9 +1815,17 @@ More examples
1695
1815
 
1696
1816
  ```sh
1697
1817
  $ dotenvx ext gitignore
1698
- creating .gitignore
1699
- appending .env* to .gitignore
1700
- done
1818
+ ignored .env* (.gitignore)
1819
+ ```
1820
+
1821
+ </details>
1822
+ * <details><summary>`ext gitignore --pattern`</summary><br>
1823
+
1824
+ Gitignore specific pattern(s) of `.env` files.
1825
+
1826
+ ```sh
1827
+ $ dotenvx ext gitignore --pattern .env.keys
1828
+ ✔ ignored .env.keys (.gitignore)
1701
1829
  ```
1702
1830
 
1703
1831
  </details>
@@ -1707,7 +1835,7 @@ More examples
1707
1835
 
1708
1836
  ```sh
1709
1837
  $ dotenvx ext precommit
1710
- [dotenvx][precommit] success
1838
+ [dotenvx][precommit] .env files (1) protected (encrypted or gitignored)
1711
1839
  ```
1712
1840
 
1713
1841
  </details>
@@ -1717,7 +1845,7 @@ More examples
1717
1845
 
1718
1846
  ```sh
1719
1847
  $ dotenvx ext precommit --install
1720
- [dotenvx][precommit] dotenvx precommit installed [.git/hooks/pre-commit]
1848
+ [dotenvx][precommit] dotenvx ext precommit installed [.git/hooks/pre-commit]
1721
1849
  ```
1722
1850
 
1723
1851
  </details>
@@ -1728,6 +1856,7 @@ More examples
1728
1856
  Add it to your `Dockerfile`.
1729
1857
 
1730
1858
  ```sh
1859
+ # Dockerfile
1731
1860
  RUN curl -fsS https://dotenvx.sh | sh
1732
1861
 
1733
1862
  ...
@@ -1780,6 +1909,8 @@ More examples
1780
1909
  Hello World
1781
1910
  ```
1782
1911
 
1912
+ It defaults to looking for a `.env` file.
1913
+
1783
1914
  </details>
1784
1915
  * <details><summary>`config(path: ['.env.local', '.env'])` - multiple files</summary><br>
1785
1916
 
@@ -1811,7 +1942,7 @@ More examples
1811
1942
  </details>
1812
1943
  * <details><summary>`config(overload: true)` - overload</summary><br>
1813
1944
 
1814
- User `overload` to overwrite the prior set value.
1945
+ Use `overload` to overwrite the prior set value.
1815
1946
 
1816
1947
  ```ini
1817
1948
  # .env.local
@@ -1858,6 +1989,44 @@ More examples
1858
1989
  Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
1859
1990
  ```
1860
1991
 
1992
+ </details>
1993
+ * <details><summary>`config(ignore:)` - ignore</summary><br>
1994
+
1995
+ Use `ignore` to suppress specific errors like `MISSING_ENV_FILE`.
1996
+
1997
+ ```ini
1998
+ # .env
1999
+ HELLO="World"
2000
+ ```
2001
+
2002
+ ```js
2003
+ // index.js
2004
+ require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], ignore: ['MISSING_ENV_FILE']})
2005
+
2006
+ console.log(`Hello ${process.env.HELLO}`)
2007
+ ```
2008
+
2009
+ ```sh
2010
+ $ node index.js
2011
+ [dotenvx@1.24.5] injecting env (1) from .env.local, .env
2012
+ Hello World
2013
+ ```
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
+
1861
2030
  </details>
1862
2031
  * <details><summary>`parse(src)`</summary><br>
1863
2032
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.28.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})`)
@@ -13,6 +13,8 @@ function get (key) {
13
13
  const options = this.opts()
14
14
  logger.debug(`options: ${JSON.stringify(options)}`)
15
15
 
16
+ const ignore = options.ignore || []
17
+
16
18
  let envs = []
17
19
  // handle shorthand conventions - like --convention=nextjs
18
20
  if (options.convention) {
@@ -22,11 +24,15 @@ function get (key) {
22
24
  }
23
25
 
24
26
  try {
25
- 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()
26
28
 
27
29
  for (const error of errors || []) {
28
30
  if (options.strict) throw error // throw immediately if strict
29
31
 
32
+ if (ignore.includes(error.code)) {
33
+ continue // ignore error
34
+ }
35
+
30
36
  console.error(error.message)
31
37
  if (error.help) {
32
38
  console.error(error.help)
@@ -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
@@ -14,6 +14,8 @@ async function run () {
14
14
  const options = this.opts()
15
15
  logger.debug(`options: ${JSON.stringify(options)}`)
16
16
 
17
+ const ignore = options.ignore || []
18
+
17
19
  if (commandArgs.length < 1) {
18
20
  const hasSeparator = process.argv.indexOf('--') !== -1
19
21
 
@@ -43,7 +45,7 @@ async function run () {
43
45
  readableStrings,
44
46
  readableFilepaths,
45
47
  uniqueInjectedKeys
46
- } = 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()
47
49
 
48
50
  for (const processedEnv of processedEnvs) {
49
51
  if (processedEnv.type === 'envVaultFile') {
@@ -62,6 +64,11 @@ async function run () {
62
64
  for (const error of processedEnv.errors || []) {
63
65
  if (options.strict) throw error // throw immediately if strict
64
66
 
67
+ if (ignore.includes(error.code)) {
68
+ logger.verbose(`ignored: ${error.message}`)
69
+ continue // ignore error
70
+ }
71
+
65
72
  if (error.code === 'MISSING_ENV_FILE') {
66
73
  if (!options.convention) { // do not output error for conventions (too noisy)
67
74
  console.error(error.message)
@@ -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,10 +56,12 @@ 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)
62
63
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\'])')
64
+ .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
63
65
  .action(function (...args) {
64
66
  this.envs = envs
65
67
  runAction.apply(this, args)
@@ -73,10 +75,12 @@ program.command('get')
73
75
  .argument('[KEY]', 'environment variable name')
74
76
  .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
75
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)')
76
79
  .option('-fv, --env-vault-file <paths...>', 'path(s) to your .env.vault file(s)', collectEnvs('envVaultFile'), [])
77
80
  .option('-o, --overload', 'override existing env variables')
78
81
  .option('--strict', 'process.exit(1) on any errors (default: false)', false)
79
82
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\'])')
83
+ .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
80
84
  .option('-a, --all', 'include all machine envs as well')
81
85
  .option('-pp, --pretty-print', 'pretty print output')
82
86
  .option('--format <type>', 'format of the output (json, shell, eval)', 'json')
@@ -95,6 +99,7 @@ program.command('set')
95
99
  .argument('KEY', 'KEY')
96
100
  .argument('value', 'value')
97
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)')
98
103
  .option('-c, --encrypt', 'encrypt value (default: true)', true)
99
104
  .option('-p, --plain', 'store value as plain text', false)
100
105
  .action(function (...args) {
@@ -107,6 +112,7 @@ const encryptAction = require('./actions/encrypt')
107
112
  program.command('encrypt')
108
113
  .description('convert .env file(s) to encrypted .env file(s)')
109
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)')
110
116
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
111
117
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
112
118
  .option('--stdout', 'send to stdout')
@@ -120,6 +126,7 @@ const decryptAction = require('./actions/decrypt')
120
126
  program.command('decrypt')
121
127
  .description('convert encrypted .env file(s) to plain .env file(s)')
122
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)')
123
130
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
124
131
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
125
132
  .option('--stdout', 'send to stdout')
@@ -135,6 +142,7 @@ program.command('keypair')
135
142
  .description('print public/private keys for .env file(s)')
136
143
  .argument('[KEY]', 'environment variable key name')
137
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)')
138
146
  .option('-pp, --pretty-print', 'pretty print output')
139
147
  .option('--format <type>', 'format of the output (json, shell)', 'json')
140
148
  .action(keypairAction)
@@ -15,7 +15,7 @@ class Errors {
15
15
  missingEnvFile () {
16
16
  const code = 'MISSING_ENV_FILE'
17
17
  const message = `[${code}] missing ${this.envFilepath} file (${this.filepath})`
18
- const help = `[${code}] ? add one with [echo "HELLO=World" > ${this.envFilepath}]`
18
+ const help = `[${code}] https://github.com/dotenvx/dotenvx/issues/484`
19
19
 
20
20
  const e = new Error(message)
21
21
  e.code = code
@@ -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
@@ -28,9 +28,15 @@ const config = function (options = {}) {
28
28
  // overload
29
29
  const overload = options.overload || options.override
30
30
 
31
+ // ignore
32
+ const ignore = options.ignore || []
33
+
31
34
  // strict
32
35
  const strict = options.strict
33
36
 
37
+ // envKeysFile
38
+ const envKeysFile = options.envKeysFile
39
+
34
40
  // DOTENV_KEY (DEPRECATED)
35
41
  let DOTENV_KEY = process.env.DOTENV_KEY
36
42
  if (options && options.DOTENV_KEY) {
@@ -67,7 +73,7 @@ const config = function (options = {}) {
67
73
  processedEnvs,
68
74
  readableFilepaths,
69
75
  uniqueInjectedKeys
70
- } = new Run(envs, overload, DOTENV_KEY, processEnv).run()
76
+ } = new Run(envs, overload, DOTENV_KEY, processEnv, envKeysFile).run()
71
77
 
72
78
  let lastError
73
79
  /** @type {Record<string, string>} */
@@ -86,19 +92,23 @@ const config = function (options = {}) {
86
92
  for (const error of processedEnv.errors || []) {
87
93
  if (strict) throw error // throw immediately if strict
88
94
 
95
+ if (ignore.includes(error.code)) {
96
+ continue // ignore error
97
+ }
98
+
89
99
  lastError = error // surface later in { error }
90
100
 
91
101
  if (error.code === 'MISSING_ENV_FILE') {
92
102
  if (!options.convention) { // do not output error for conventions (too noisy)
93
103
  console.error(error.message)
94
104
  if (error.help) {
95
- logger.help(error.help)
105
+ console.error(error.help)
96
106
  }
97
107
  }
98
108
  } else {
99
109
  console.error(error.message)
100
110
  if (error.help) {
101
- logger.help(error.help)
111
+ console.error(error.help)
102
112
  }
103
113
  }
104
114
  }
@@ -183,8 +193,13 @@ const genexample = function (directory, envFile) {
183
193
  }
184
194
 
185
195
  /** @type {import('./main').keypair} */
186
- const keypair = function (envFile, key) {
187
- 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
+ }
188
203
  }
189
204
 
190
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