@dotenvx/dotenvx 1.24.5 → 1.25.1

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,29 @@
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.24.5...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.25.1...main)
6
+
7
+ ## [1.25.1](https://github.com/dotenvx/dotenvx/compare/v1.25.0...v1.25.1)
8
+
9
+ ### Changed
10
+
11
+ * improve helpful error messaging around decryption failures by specifying specific key and private key name ([#463](https://github.com/dotenvx/dotenvx/pull/463))
12
+
13
+ ## [1.25.0](https://github.com/dotenvx/dotenvx/compare/v1.24.5...v1.25.0)
14
+
15
+ ### Added
16
+
17
+ * add `run --strict` flag to exit with code `1` if any errors are encountered - like a missing `.env` file or decryption failure ([#460](https://github.com/dotenvx/dotenvx/pull/460))
18
+ * add `get --strict` flag to exit with code `1` if any errors are encountered - like a missing `.env` file or decryption failure ([#461](https://github.com/dotenvx/dotenvx/pull/461))
19
+ * add `strict` option to `config()` to throw for any errors ([#459](https://github.com/dotenvx/dotenvx/pull/459))
20
+
21
+ ### Changed
22
+
23
+ * log `MISSING_ENV_FILE` and `DECRYPTION_FAILED` errors to stderr (prior was stdout as a warning) ([#459](https://github.com/dotenvx/dotenvx/pull/459))
24
+
25
+ ### Removed
26
+
27
+ * remove `dotenvx.get()` function from `lib/main.js`. (`parse` already historically exists for this purpose) ([#461](https://github.com/dotenvx/dotenvx/pull/461))
6
28
 
7
29
  ## [1.24.5](https://github.com/dotenvx/dotenvx/compare/v1.24.4...v1.24.5)
8
30
 
package/README.md CHANGED
@@ -968,6 +968,21 @@ More examples
968
968
 
969
969
  Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
970
970
 
971
+ </details>
972
+ * <details><summary>`run --strict`</summary><br>
973
+
974
+ Exit with code `1` if any errors are encountered - like a missing .env file or decryption failure.
975
+
976
+ ```sh
977
+ $ echo "console.log('Hello ' + process.env.HELLO)" > index.js
978
+
979
+ $ dotenvx run -f .env.missing --strict -- node index.js
980
+ [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
981
+ [MISSING_ENV_FILE] ? add one with [echo "HELLO=World" > .env.missing]
982
+ ```
983
+
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
+
971
986
  </details>
972
987
  * <details><summary>`run --convention=nextjs`</summary><br>
973
988
 
@@ -1036,6 +1051,16 @@ More examples
1036
1051
  World
1037
1052
  ```
1038
1053
 
1054
+ </details>
1055
+ * <details><summary>`get KEY --strict`</summary><br>
1056
+
1057
+ Exit with code `1` if any errors are encountered - like a missing key, missing .env file, or decryption failure.
1058
+
1059
+ ```sh
1060
+ $ dotenvx get DOES_NOT_EXIST --strict
1061
+ [MISSING_KEY] missing DOES_NOT_EXIST key
1062
+ ```
1063
+
1039
1064
  </details>
1040
1065
  * <details><summary>`get KEY --convention=nextjs`</summary><br>
1041
1066
 
@@ -1731,6 +1756,112 @@ More examples
1731
1756
 
1732
1757
  </details>
1733
1758
 
1759
+ ### config() 📦
1760
+
1761
+ * <details><summary>`config()`</summary><br>
1762
+
1763
+ Use directly in node.js code.
1764
+
1765
+ ```ini
1766
+ # .env
1767
+ HELLO="World"
1768
+ ```
1769
+
1770
+ ```js
1771
+ // index.js
1772
+ require('@dotenvx/dotenvx').config()
1773
+
1774
+ console.log(`Hello ${process.env.HELLO}`)
1775
+ ```
1776
+
1777
+ ```sh
1778
+ $ node index.js
1779
+ [dotenvx@1.24.5] injecting env (1) from .env
1780
+ Hello World
1781
+ ```
1782
+
1783
+ </details>
1784
+ * <details><summary>`config(path: ['.env.local', '.env'])` - multiple files</summary><br>
1785
+
1786
+ Specify path(s) to multiple .env files.
1787
+
1788
+ ```ini
1789
+ # .env.local
1790
+ HELLO="Me"
1791
+ ```
1792
+
1793
+ ```ini
1794
+ # .env
1795
+ HELLO="World"
1796
+ ```
1797
+
1798
+ ```js
1799
+ // index.js
1800
+ require('@dotenvx/dotenvx').config({path: ['.env.local', '.env']})
1801
+
1802
+ console.log(`Hello ${process.env.HELLO}`)
1803
+ ```
1804
+
1805
+ ```sh
1806
+ $ node index.js
1807
+ [dotenvx@1.24.5] injecting env (1) from .env.local, .env
1808
+ Hello Me
1809
+ ```
1810
+
1811
+ </details>
1812
+ * <details><summary>`config(overload: true)` - overload</summary><br>
1813
+
1814
+ User `overload` to overwrite the prior set value.
1815
+
1816
+ ```ini
1817
+ # .env.local
1818
+ HELLO="Me"
1819
+ ```
1820
+
1821
+ ```ini
1822
+ # .env
1823
+ HELLO="World"
1824
+ ```
1825
+
1826
+ ```js
1827
+ // index.js
1828
+ require('@dotenvx/dotenvx').config({path: ['.env.local', '.env'], overload: true})
1829
+
1830
+ console.log(`Hello ${process.env.HELLO}`)
1831
+ ```
1832
+
1833
+ ```sh
1834
+ $ node index.js
1835
+ [dotenvx@1.24.5] injecting env (1) from .env.local, .env
1836
+ Hello World
1837
+ ```
1838
+
1839
+ </details>
1840
+ * <details><summary>`config(strict: true)` - strict</summary><br>
1841
+
1842
+ Use `strict` to throw if an error is encountered - like a missing .env file.
1843
+
1844
+ ```ini
1845
+ # .env
1846
+ HELLO="World"
1847
+ ```
1848
+
1849
+ ```js
1850
+ // index.js
1851
+ require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], strict: true})
1852
+
1853
+ console.log(`Hello ${process.env.HELLO}`)
1854
+ ```
1855
+
1856
+ ```sh
1857
+ $ node index.js
1858
+ Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
1859
+ ```
1860
+
1861
+ </details>
1862
+
1863
+
1864
+
1734
1865
  &nbsp;
1735
1866
 
1736
1867
  ## Guides
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.24.5",
2
+ "version": "1.25.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -48,10 +48,10 @@ function decrypt () {
48
48
  errorCount += 1
49
49
 
50
50
  if (processedEnv.error.code === 'MISSING_ENV_FILE') {
51
- logger.error(processedEnv.error.message)
51
+ console.error(processedEnv.error.message)
52
52
  logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx decrypt]`)
53
53
  } else {
54
- logger.error(processedEnv.error.message)
54
+ console.error(processedEnv.error.message)
55
55
  }
56
56
  } else if (processedEnv.changed) {
57
57
  fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
@@ -26,7 +26,7 @@ function genexample (directory) {
26
26
  logger.blank('no changes (.env.example)')
27
27
  }
28
28
  } catch (error) {
29
- logger.error(error.message)
29
+ console.error(error.message)
30
30
  if (error.help) {
31
31
  logger.help(error.help)
32
32
  }
@@ -10,7 +10,7 @@ function scan () {
10
10
  // redirect stderr to stdout to capture and ignore it
11
11
  childProcess.execSync('gitleaks version', { stdio: ['ignore', 'pipe', 'ignore'] })
12
12
  } catch (error) {
13
- logger.error('gitleaks: command not found')
13
+ console.error('gitleaks: command not found')
14
14
  logger.help('? install gitleaks: [brew install gitleaks]')
15
15
  logger.help2('? other install options: [https://github.com/gitleaks/gitleaks]')
16
16
  process.exit(1)
@@ -21,7 +21,7 @@ function scan () {
21
21
  const output = childProcess.execSync('gitleaks detect -v 2>&1', { stdio: 'pipe' }).toString() // gitleaks sends output as stderr for strange reason
22
22
  logger.blank(output)
23
23
  } catch (error) {
24
- logger.error(error.message)
24
+ console.error(error.message)
25
25
 
26
26
  process.exit(1)
27
27
  }
@@ -3,7 +3,7 @@ const { logger } = require('./../../shared/logger')
3
3
  const conventions = require('./../../lib/helpers/conventions')
4
4
  const escape = require('./../../lib/helpers/escape')
5
5
 
6
- const main = require('./../../lib/main')
6
+ const Get = require('./../../lib/services/get')
7
7
 
8
8
  function get (key) {
9
9
  if (key) {
@@ -21,40 +21,57 @@ function get (key) {
21
21
  envs = this.envs
22
22
  }
23
23
 
24
- const results = main.get(key, envs, options.overload, process.env.DOTENV_KEY, options.all)
24
+ try {
25
+ const { parsed, errors } = new Get(key, envs, options.overload, process.env.DOTENV_KEY, options.all).run()
25
26
 
26
- if (typeof results === 'object' && results !== null) {
27
- if (options.format === 'eval') {
28
- let inline = ''
29
- for (const [key, value] of Object.entries(results)) {
30
- inline += `${key}=${escape(value)}\n`
31
- }
32
- inline = inline.trim()
27
+ for (const error of errors || []) {
28
+ if (options.strict) throw error // throw immediately if strict
33
29
 
34
- console.log(inline)
35
- } else if (options.format === 'shell') {
36
- let inline = ''
37
- for (const [key, value] of Object.entries(results)) {
38
- inline += `${key}=${value} `
30
+ console.error(error.message)
31
+ if (error.help) {
32
+ console.error(error.help)
39
33
  }
40
- inline = inline.trim()
34
+ }
41
35
 
42
- console.log(inline)
43
- } else {
44
- let space = 0
45
- if (options.prettyPrint) {
46
- space = 2
36
+ if (key) {
37
+ const single = parsed[key]
38
+ if (single === undefined) {
39
+ console.log('')
40
+ } else {
41
+ console.log(single)
47
42
  }
43
+ } else {
44
+ if (options.format === 'eval') {
45
+ let inline = ''
46
+ for (const [key, value] of Object.entries(parsed)) {
47
+ inline += `${key}=${escape(value)}\n`
48
+ }
49
+ inline = inline.trim()
50
+
51
+ console.log(inline)
52
+ } else if (options.format === 'shell') {
53
+ let inline = ''
54
+ for (const [key, value] of Object.entries(parsed)) {
55
+ inline += `${key}=${value} `
56
+ }
57
+ inline = inline.trim()
48
58
 
49
- console.log(JSON.stringify(results, null, space))
59
+ console.log(inline)
60
+ } else {
61
+ let space = 0
62
+ if (options.prettyPrint) {
63
+ space = 2
64
+ }
65
+
66
+ console.log(JSON.stringify(parsed, null, space))
67
+ }
50
68
  }
51
- } else {
52
- if (results === undefined) {
53
- console.log('')
54
- process.exit(1)
55
- } else {
56
- console.log(results)
69
+ } catch (error) {
70
+ console.error(error.message)
71
+ if (error.help) {
72
+ console.error(error.help)
57
73
  }
74
+ process.exit(1)
58
75
  }
59
76
  }
60
77
 
@@ -5,6 +5,7 @@ const executeCommand = require('./../../lib/helpers/executeCommand')
5
5
  const Run = require('./../../lib/services/run')
6
6
 
7
7
  const conventions = require('./../../lib/helpers/conventions')
8
+ const DeprecationNotice = require('./../../lib/helpers/deprecationNotice')
8
9
 
9
10
  async function run () {
10
11
  const commandArgs = this.args
@@ -35,11 +36,7 @@ async function run () {
35
36
  envs = this.envs
36
37
  }
37
38
 
38
- if (process.env.DOTENV_KEY) {
39
- logger.warn('DEPRECATION NOTICE: Setting DOTENV_KEY with .env.vault is deprecated.')
40
- logger.warn('DEPRECATION NOTICE: Run [dotenvx ext vault migrate] for instructions on converting your .env.vault file to encrypted .env files (using public key encryption algorithm secp256k1)')
41
- logger.warn('DEPRECATION NOTICE: Read more at [https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md#0380]')
42
- }
39
+ new DeprecationNotice().dotenvKey() // DEPRECATION NOTICE
43
40
 
44
41
  const {
45
42
  processedEnvs,
@@ -62,43 +59,37 @@ async function run () {
62
59
  logger.verbose(`loading env from string (${processedEnv.string})`)
63
60
  }
64
61
 
65
- if (processedEnv.error) {
66
- if (processedEnv.error.code === 'MISSING_ENV_FILE') {
67
- // do not warn for conventions (too noisy)
68
- if (!options.convention) {
69
- logger.warnv(processedEnv.error.message)
70
- logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.filepath}] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
62
+ for (const error of processedEnv.errors || []) {
63
+ if (options.strict) throw error // throw immediately if strict
64
+
65
+ if (error.code === 'MISSING_ENV_FILE') {
66
+ if (!options.convention) { // do not output error for conventions (too noisy)
67
+ console.error(error.message)
68
+ if (error.help) {
69
+ console.error(`${error.help} and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
70
+ }
71
71
  }
72
72
  } else {
73
- logger.warnv(processedEnv.error.message)
74
- }
75
- } else {
76
- if (processedEnv.warnings) {
77
- for (const warning of processedEnv.warnings) {
78
- logger.warn(warning.message)
79
- if (warning.help) {
80
- logger.help(warning.help)
81
- }
73
+ console.error(error.message)
74
+ if (error.help) {
75
+ console.error(error.help)
82
76
  }
83
77
  }
78
+ }
84
79
 
85
- // debug parsed
86
- const parsed = processedEnv.parsed
87
- logger.debug(parsed)
80
+ // debug parsed
81
+ logger.debug(processedEnv.parsed)
88
82
 
89
- // verbose/debug injected key/value
90
- const injected = processedEnv.injected
91
- for (const [key, value] of Object.entries(injected)) {
92
- logger.verbose(`${key} set`)
93
- logger.debug(`${key} set to ${value}`)
94
- }
83
+ // verbose/debug injected key/value
84
+ for (const [key, value] of Object.entries(processedEnv.injected || {})) {
85
+ logger.verbose(`${key} set`)
86
+ logger.debug(`${key} set to ${value}`)
87
+ }
95
88
 
96
- // verbose/debug preExisted key/value
97
- const preExisted = processedEnv.preExisted
98
- for (const [key, value] of Object.entries(preExisted)) {
99
- logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
100
- logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
101
- }
89
+ // verbose/debug preExisted key/value
90
+ for (const [key, value] of Object.entries(processedEnv.preExisted || {})) {
91
+ logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
92
+ logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
102
93
  }
103
94
  }
104
95
 
@@ -113,10 +104,11 @@ async function run () {
113
104
 
114
105
  logger.successv(msg)
115
106
  } catch (error) {
116
- logger.error(error.message)
107
+ console.error(error.message)
117
108
  if (error.help) {
118
- logger.help(error.help)
109
+ console.error(error.help)
119
110
  }
111
+ process.exit(1)
120
112
  }
121
113
 
122
114
  await executeCommand(commandArgs, process.env)
@@ -58,6 +58,7 @@ program.command('run')
58
58
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
59
59
  .option('-fv, --env-vault-file <paths...>', 'path(s) to your .env.vault file(s)', collectEnvs('envVaultFile'), [])
60
60
  .option('-o, --overload', 'override existing env variables')
61
+ .option('--strict', 'process.exit(1) on any errors (default: false)', false)
61
62
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\'])')
62
63
  .action(function (...args) {
63
64
  this.envs = envs
@@ -74,6 +75,7 @@ program.command('get')
74
75
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
75
76
  .option('-fv, --env-vault-file <paths...>', 'path(s) to your .env.vault file(s)', collectEnvs('envVaultFile'), [])
76
77
  .option('-o, --overload', 'override existing env variables')
78
+ .option('--strict', 'process.exit(1) on any errors (default: false)', false)
77
79
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\'])')
78
80
  .option('-a, --all', 'include all machine envs as well')
79
81
  .option('-pp, --pretty-print', 'pretty print output')
@@ -1,7 +1,7 @@
1
1
  const { logger } = require('./../../shared/logger')
2
2
 
3
3
  function catchAndLog (error) {
4
- logger.error(error.message)
4
+ console.error(error.message)
5
5
  if (error.help) {
6
6
  logger.help(error.help)
7
7
  }
@@ -1,10 +1,10 @@
1
1
  const { decrypt } = require('eciesjs')
2
2
 
3
- const truncate = require('./truncate')
3
+ const Errors = require('./errors')
4
4
 
5
5
  const PREFIX = 'encrypted:'
6
6
 
7
- function decryptValue (value, privateKey) {
7
+ function decryptKeyValue (key, value, privateKeyName, privateKey) {
8
8
  let decryptedValue
9
9
  let decryptionError
10
10
 
@@ -14,12 +14,11 @@ function decryptValue (value, privateKey) {
14
14
 
15
15
  privateKey = privateKey || ''
16
16
  if (privateKey.length <= 0) {
17
- decryptionError = new Error('private key missing or blank')
18
- decryptionError.code = 'DECRYPTION_FAILED'
17
+ decryptionError = new Errors({ key, privateKeyName, privateKey }).missingPrivateKey()
19
18
  } else {
20
19
  const privateKeys = privateKey.split(',')
21
- for (const key of privateKeys) {
22
- const secret = Buffer.from(key, 'hex')
20
+ for (const privKey of privateKeys) {
21
+ const secret = Buffer.from(privKey, 'hex')
23
22
  const encoded = value.substring(PREFIX.length)
24
23
  const ciphertext = Buffer.from(encoded, 'base64')
25
24
 
@@ -29,16 +28,14 @@ function decryptValue (value, privateKey) {
29
28
  break
30
29
  } catch (e) {
31
30
  if (e.message === 'Invalid private key') {
32
- decryptionError = new Error(`private key [${truncate(privateKey)}] looks invalid`)
31
+ decryptionError = new Errors({ key, privateKeyName, privateKey }).invalidPrivateKey()
33
32
  } else if (e.message === 'Unsupported state or unable to authenticate data') {
34
- decryptionError = new Error(`private key [${truncate(privateKey)}] looks wrong`)
33
+ decryptionError = new Errors({ key, privateKeyName, privateKey }).looksWrongPrivateKey()
35
34
  } else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {
36
- decryptionError = new Error('encrypted data looks malformed')
35
+ decryptionError = new Errors({ key, privateKeyName, privateKey }).malformedEncryptedData()
37
36
  } else {
38
- decryptionError = new Error(`${e.message}`)
37
+ decryptionError = new Errors({ key, privateKeyName, privateKey, message: e.message }).decryptionFailed()
39
38
  }
40
-
41
- decryptionError.code = 'DECRYPTION_FAILED'
42
39
  }
43
40
  }
44
41
  }
@@ -50,4 +47,4 @@ function decryptValue (value, privateKey) {
50
47
  return decryptedValue
51
48
  }
52
49
 
53
- module.exports = decryptValue
50
+ module.exports = decryptKeyValue
@@ -0,0 +1,17 @@
1
+ const { logger } = require('./../../shared/logger')
2
+
3
+ class DeprecationNotice {
4
+ constructor (options = {}) {
5
+ this.DOTENV_KEY = options.DOTENV_KEY || process.env.DOTENV_KEY
6
+ }
7
+
8
+ dotenvKey () {
9
+ if (this.DOTENV_KEY) {
10
+ logger.warn('[DEPRECATION NOTICE] Setting DOTENV_KEY with .env.vault is deprecated.')
11
+ logger.warn('[DEPRECATION NOTICE] Run [dotenvx ext vault migrate] for instructions on converting your .env.vault file to encrypted .env files (using public key encryption algorithm secp256k1)')
12
+ logger.warn('[DEPRECATION NOTICE] Read more at [https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md#0380]')
13
+ }
14
+ }
15
+ }
16
+
17
+ module.exports = DeprecationNotice
@@ -0,0 +1,89 @@
1
+ const truncate = require('./truncate')
2
+
3
+ class Errors {
4
+ constructor (options = {}) {
5
+ this.filepath = options.filepath
6
+ this.envFilepath = options.envFilepath
7
+
8
+ this.key = options.key
9
+ this.privateKey = options.privateKey
10
+ this.privateKeyName = options.privateKeyName
11
+
12
+ this.message = options.message
13
+ }
14
+
15
+ missingEnvFile () {
16
+ const code = 'MISSING_ENV_FILE'
17
+ const message = `[${code}] missing ${this.envFilepath} file (${this.filepath})`
18
+ const help = `[${code}] ? add one with [echo "HELLO=World" > ${this.envFilepath}]`
19
+
20
+ const e = new Error(message)
21
+ e.code = code
22
+ e.help = help
23
+ return e
24
+ }
25
+
26
+ missingKey () {
27
+ const code = 'MISSING_KEY'
28
+ const message = `[${code}] missing ${this.key} key`
29
+
30
+ const e = new Error(message)
31
+ e.code = code
32
+ return e
33
+ }
34
+
35
+ missingPrivateKey () {
36
+ const code = 'MISSING_PRIVATE_KEY'
37
+ const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
38
+ const help = `[${code}] https://github.com/dotenvx/dotenvx/issues/464`
39
+
40
+ const e = new Error(message)
41
+ e.code = code
42
+ e.help = help
43
+ return e
44
+ }
45
+
46
+ invalidPrivateKey () {
47
+ const code = 'INVALID_PRIVATE_KEY'
48
+ const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
49
+ const help = `[${code}] https://github.com/dotenvx/dotenvx/issues/465`
50
+
51
+ const e = new Error(message)
52
+ e.code = code
53
+ e.help = help
54
+ return e
55
+ }
56
+
57
+ looksWrongPrivateKey () {
58
+ const code = 'WRONG_PRIVATE_KEY'
59
+ const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
60
+ const help = `[${code}] https://github.com/dotenvx/dotenvx/issues/466`
61
+
62
+ const e = new Error(message)
63
+ e.code = code
64
+ e.help = help
65
+ return e
66
+ }
67
+
68
+ malformedEncryptedData () {
69
+ const code = 'MALFORMED_ENCRYPTED_DATA'
70
+ const message = `[${code}] could not decrypt ${this.key} because encrypted data appears malformed`
71
+ const help = `[${code}] https://github.com/dotenvx/dotenvx/issues/467`
72
+
73
+ const e = new Error(message)
74
+ e.code = code
75
+ e.help = help
76
+ return e
77
+ }
78
+
79
+ decryptionFailed () {
80
+ const code = 'DECRYPTION_FAILED'
81
+ const message = this.message
82
+
83
+ const e = new Error(message)
84
+ e.code = code
85
+ return e
86
+ }
87
+ }
88
+
89
+ module.exports = Errors
@@ -1,22 +1,22 @@
1
1
  const chomp = require('./chomp')
2
- const truncate = require('./truncate')
3
- const decryptValue = require('./decryptValue')
2
+ const decryptKeyValue = require('./decryptKeyValue')
4
3
  const resolveEscapeSequences = require('./resolveEscapeSequences')
5
4
  const { execSync } = require('child_process')
6
5
 
7
6
  class Parse {
8
7
  static LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
9
8
 
10
- constructor (src, privateKey = null, processEnv = process.env, overload = false) {
9
+ constructor (src, privateKey = null, processEnv = process.env, overload = false, privateKeyName = null) {
11
10
  this.src = src
12
11
  this.privateKey = privateKey
12
+ this.privateKeyName = privateKeyName
13
13
  this.processEnv = processEnv
14
14
  this.overload = overload
15
15
 
16
16
  this.parsed = {}
17
17
  this.preExisted = {}
18
18
  this.injected = {}
19
- this.warnings = []
19
+ this.errors = []
20
20
 
21
21
  // for use with progressive expansion
22
22
  this.runningParsed = {}
@@ -40,9 +40,9 @@ class Parse {
40
40
 
41
41
  // decrypt
42
42
  try {
43
- this.parsed[key] = this.decrypt(this.parsed[key])
43
+ this.parsed[key] = this.decrypt(key, this.parsed[key])
44
44
  } catch (e) {
45
- this.warnings.push(this.warning(e, key))
45
+ this.errors.push(e)
46
46
  }
47
47
 
48
48
  // eval empty, double, or backticks
@@ -78,8 +78,8 @@ class Parse {
78
78
  parsed: this.parsed,
79
79
  processEnv: this.processEnv,
80
80
  injected: this.injected,
81
- warnings: this.warnings,
82
- preExisted: this.preExisted
81
+ preExisted: this.preExisted,
82
+ errors: this.errors
83
83
  }
84
84
  }
85
85
 
@@ -128,8 +128,8 @@ class Parse {
128
128
  return v
129
129
  }
130
130
 
131
- decrypt (value) {
132
- return decryptValue(value, this.privateKey)
131
+ decrypt (key, value) {
132
+ return decryptKeyValue(key, value, this.privateKeyName, this.privateKey)
133
133
  }
134
134
 
135
135
  eval (value) {
@@ -207,14 +207,6 @@ class Parse {
207
207
  getLines () {
208
208
  return (this.src || '').toString().replace(/\r\n?/mg, '\n') // Convert buffer to string and Convert line breaks to same format
209
209
  }
210
-
211
- warning (e, key) {
212
- const warning = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(this.privateKey)}'`)
213
- warning.code = e.code
214
- warning.help = `[${e.code}] ? ${e.message}`
215
-
216
- return warning
217
- }
218
210
  }
219
211
 
220
212
  module.exports = Parse
package/src/lib/main.js CHANGED
@@ -7,7 +7,6 @@ const { getColor, bold } = require('./../shared/colors')
7
7
 
8
8
  // services
9
9
  const Ls = require('./services/ls')
10
- const Get = require('./services/get')
11
10
  const Run = require('./services/run')
12
11
  const Keypair = require('./services/keypair')
13
12
  const Genexample = require('./services/genexample')
@@ -16,6 +15,7 @@ const Genexample = require('./services/genexample')
16
15
  const conventions = require('./helpers/conventions')
17
16
  const dotenvOptionPaths = require('./helpers/dotenvOptionPaths')
18
17
  const Parse = require('./helpers/parse')
18
+ const DeprecationNotice = require('./helpers/deprecationNotice')
19
19
 
20
20
  /** @type {import('./main').config} */
21
21
  const config = function (options = {}) {
@@ -28,7 +28,10 @@ const config = function (options = {}) {
28
28
  // overload
29
29
  const overload = options.overload || options.override
30
30
 
31
- // DOTENV_KEY
31
+ // strict
32
+ const strict = options.strict
33
+
34
+ // DOTENV_KEY (DEPRECATED)
32
35
  let DOTENV_KEY = process.env.DOTENV_KEY
33
36
  if (options && options.DOTENV_KEY) {
34
37
  DOTENV_KEY = options.DOTENV_KEY
@@ -46,11 +49,7 @@ const config = function (options = {}) {
46
49
  envs = conventions(options.convention).concat(envs)
47
50
  }
48
51
 
49
- if (process.env.DOTENV_KEY) {
50
- logger.warn('DEPRECATION NOTICE: Setting DOTENV_KEY with .env.vault is deprecated.')
51
- logger.warn('DEPRECATION NOTICE: Run [dotenvx ext vault migrate] for instructions on converting your .env.vault file to encrypted .env files (using public key encryption algorithm secp256k1)')
52
- logger.warn('DEPRECATION NOTICE: Read more at [https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md#0380]')
53
- }
52
+ new DeprecationNotice({ DOTENV_KEY }).dotenvKey() // DEPRECATION NOTICE
54
53
 
55
54
  for (const optionPath of optionPaths) {
56
55
  // if DOTENV_KEY is set then assume we are checking envVaultFile
@@ -64,12 +63,11 @@ const config = function (options = {}) {
64
63
  }
65
64
  }
66
65
 
67
- const { processedEnvs, readableFilepaths, uniqueInjectedKeys } = new Run(
68
- envs,
69
- overload,
70
- DOTENV_KEY,
71
- processEnv
72
- ).run()
66
+ const {
67
+ processedEnvs,
68
+ readableFilepaths,
69
+ uniqueInjectedKeys
70
+ } = new Run(envs, overload, DOTENV_KEY, processEnv).run()
73
71
 
74
72
  let lastError
75
73
  /** @type {Record<string, string>} */
@@ -77,65 +75,50 @@ const config = function (options = {}) {
77
75
 
78
76
  for (const processedEnv of processedEnvs) {
79
77
  if (processedEnv.type === 'envVaultFile') {
80
- logger.verbose(
81
- `loading env from encrypted ${processedEnv.filepath} (${path.resolve(
82
- processedEnv.filepath
83
- )})`
84
- )
85
- logger.debug(
86
- `decrypting encrypted env from ${
87
- processedEnv.filepath
88
- } (${path.resolve(processedEnv.filepath)})`
89
- )
78
+ logger.verbose(`loading env from encrypted ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
79
+ logger.debug(`decrypting encrypted env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
90
80
  }
91
81
 
92
82
  if (processedEnv.type === 'envFile') {
93
- logger.verbose(
94
- `loading env from ${processedEnv.filepath} (${path.resolve(
95
- processedEnv.filepath
96
- )})`
97
- )
83
+ logger.verbose(`loading env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
98
84
  }
99
85
 
100
- if (processedEnv.error) {
101
- lastError = processedEnv.error
86
+ for (const error of processedEnv.errors || []) {
87
+ if (strict) throw error // throw immediately if strict
102
88
 
103
- if (processedEnv.error.code === 'MISSING_ENV_FILE') {
104
- // do not warn for conventions (too noisy)
105
- if (!options.convention) {
106
- logger.warnv(processedEnv.error.message)
107
- logger.help(
108
- `? add one with [echo "HELLO=World" > ${processedEnv.filepath}] and re-run [dotenvx run -- yourcommand]`
109
- )
89
+ lastError = error // surface later in { error }
90
+
91
+ if (error.code === 'MISSING_ENV_FILE') {
92
+ if (!options.convention) { // do not output error for conventions (too noisy)
93
+ console.error(error.message)
94
+ if (error.help) {
95
+ logger.help(error.help)
96
+ }
110
97
  }
111
98
  } else {
112
- logger.warnv(processedEnv.error.message)
113
- }
114
- } else {
115
- Object.assign(parsedAll, processedEnv.injected)
116
- Object.assign(parsedAll, processedEnv.preExisted) // preExisted 'wins'
117
-
118
- // debug parsed
119
- const parsed = processedEnv.parsed
120
- logger.debug(parsed)
121
-
122
- // verbose/debug injected key/value
123
- const injected = processedEnv.injected
124
- for (const [key, value] of Object.entries(injected)) {
125
- logger.verbose(`${key} set`)
126
- logger.debug(`${key} set to ${value}`)
99
+ console.error(error.message)
100
+ if (error.help) {
101
+ logger.help(error.help)
102
+ }
127
103
  }
104
+ }
128
105
 
129
- // verbose/debug preExisted key/value
130
- const preExisted = processedEnv.preExisted
131
- for (const [key, value] of Object.entries(preExisted)) {
132
- logger.verbose(
133
- `${key} pre-exists (protip: use --overload to override)`
134
- )
135
- logger.debug(
136
- `${key} pre-exists as ${value} (protip: use --overload to override)`
137
- )
138
- }
106
+ Object.assign(parsedAll, processedEnv.injected || {})
107
+ Object.assign(parsedAll, processedEnv.preExisted || {}) // preExisted 'wins'
108
+
109
+ // debug parsed
110
+ logger.debug(processedEnv.parsed)
111
+
112
+ // verbose/debug injected key/value
113
+ for (const [key, value] of Object.entries(processedEnv.injected || {})) {
114
+ logger.verbose(`${key} set`)
115
+ logger.debug(`${key} set to ${value}`)
116
+ }
117
+
118
+ // verbose/debug preExisted key/value
119
+ for (const [key, value] of Object.entries(processedEnv.preExisted || {})) {
120
+ logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
121
+ logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
139
122
  }
140
123
  }
141
124
 
@@ -151,6 +134,8 @@ const config = function (options = {}) {
151
134
  return { parsed: parsedAll }
152
135
  }
153
136
  } catch (error) {
137
+ if (strict) throw error // throw immediately if strict
138
+
154
139
  logger.error(error.message)
155
140
  if (error.help) {
156
141
  logger.help(error.help)
@@ -189,17 +174,6 @@ const genexample = function (directory, envFile) {
189
174
  return new Genexample(directory, envFile).run()
190
175
  }
191
176
 
192
- /** @type {import('./main').get} */
193
- const get = function (
194
- key,
195
- envs = [],
196
- overload = false,
197
- DOTENV_KEY = '',
198
- all = false
199
- ) {
200
- return new Get(key, envs, overload, DOTENV_KEY, all).run()
201
- }
202
-
203
177
  /** @type {import('./main').keypair} */
204
178
  const keypair = function (envFile, key) {
205
179
  return new Keypair(envFile, key).run()
@@ -211,7 +185,6 @@ module.exports = {
211
185
  parse,
212
186
  // actions related
213
187
  ls,
214
- get,
215
188
  keypair,
216
189
  genexample,
217
190
  // expose for libs depending on @dotenvx/dotenvx - like dotenvx-pro
@@ -5,9 +5,10 @@ const picomatch = require('picomatch')
5
5
 
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
 
8
+ const Errors = require('./../helpers/errors')
8
9
  const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
9
10
  const findPrivateKey = require('./../helpers/findPrivateKey')
10
- const decryptValue = require('./../helpers/decryptValue')
11
+ const decryptKeyValue = require('./../helpers/decryptKeyValue')
11
12
  const isEncrypted = require('./../helpers/isEncrypted')
12
13
  const replace = require('./../helpers/replace')
13
14
  const detectEncoding = require('./../helpers/detectEncoding')
@@ -85,7 +86,7 @@ class Decrypt {
85
86
  if (encrypted) {
86
87
  row.keys.push(key) // track key(s)
87
88
 
88
- const decryptedValue = decryptValue(value, privateKey)
89
+ const decryptedValue = decryptKeyValue(key, value, privateKeyName, privateKey)
89
90
  // once newSrc is built write it out
90
91
  envSrc = replace(envSrc, key, decryptedValue)
91
92
 
@@ -101,10 +102,7 @@ class Decrypt {
101
102
  }
102
103
  } catch (e) {
103
104
  if (e.code === 'ENOENT') {
104
- const error = new Error(`missing ${envFilepath} file (${filepath})`)
105
- error.code = 'MISSING_ENV_FILE'
106
-
107
- row.error = error
105
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
108
106
  } else {
109
107
  row.error = e
110
108
  }
@@ -5,6 +5,7 @@ const picomatch = require('picomatch')
5
5
 
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
 
8
+ const Errors = require('./../helpers/errors')
8
9
  const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
9
10
  const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
10
11
  const encryptValue = require('./../helpers/encryptValue')
@@ -181,10 +182,7 @@ class Encrypt {
181
182
  }
182
183
  } catch (e) {
183
184
  if (e.code === 'ENOENT') {
184
- const error = new Error(`missing ${envFilepath} file (${filepath})`)
185
- error.code = 'MISSING_ENV_FILE'
186
-
187
- row.error = error
185
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
188
186
  } else {
189
187
  row.error = e
190
188
  }
@@ -2,6 +2,7 @@ const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
3
  const dotenv = require('dotenv')
4
4
 
5
+ const Errors = require('../helpers/errors')
5
6
  const findEnvFiles = require('../helpers/findEnvFiles')
6
7
  const replace = require('../helpers/replace')
7
8
 
@@ -39,13 +40,8 @@ class Genexample {
39
40
  for (const envFilepath of envFilepaths) {
40
41
  const filepath = path.resolve(this.directory, envFilepath)
41
42
  if (!fsx.existsSync(filepath)) {
42
- const code = 'MISSING_ENV_FILE'
43
- const message = `file does not exist at [${filepath}]`
44
- const help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx genexample]`
45
-
46
- const error = new Error(message)
47
- error.code = code
48
- error.help = help
43
+ const error = new Errors({ envFilepath, filepath }).missingEnvFile()
44
+ error.help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx genexample]`
49
45
  throw error
50
46
  }
51
47
 
@@ -1,41 +1,58 @@
1
1
  const Run = require('./run')
2
+ const Errors = require('./../helpers/errors')
2
3
 
3
4
  class Get {
4
- constructor (key, envs = [], overload = false, DOTENV_KEY = '', all = false) {
5
+ constructor (key, envs = [], overload = false, DOTENV_KEY = '', all = false, strict = false) {
5
6
  this.key = key
6
7
  this.envs = envs
7
8
  this.overload = overload
8
9
  this.DOTENV_KEY = DOTENV_KEY
9
10
  this.all = all
11
+ this.strict = strict
10
12
  }
11
13
 
12
14
  run () {
13
15
  const processEnv = { ...process.env }
14
16
  const { processedEnvs } = new Run(this.envs, this.overload, this.DOTENV_KEY, processEnv).run()
15
17
 
16
- if (!this.key) {
18
+ const errors = []
19
+ for (const processedEnv of processedEnvs) {
20
+ for (const error of processedEnv.errors) {
21
+ errors.push(error)
22
+ }
23
+ }
24
+
25
+ if (this.key) {
26
+ const parsed = {}
27
+ const value = processEnv[this.key]
28
+ parsed[this.key] = value
29
+
30
+ if (value === undefined) {
31
+ errors.push(new Errors({ key: this.key }).missingKey())
32
+ }
33
+
34
+ return { parsed, errors }
35
+ } else {
17
36
  // if user wants to return ALL envs (even prior set on machine)
18
37
  if (this.all) {
19
- return processEnv
38
+ return { parsed: processEnv, errors }
20
39
  }
21
40
 
22
41
  // typical scenario - return only envs that were identified in the .env file
23
42
  // iterate over all processedEnvs.parsed and grab from processEnv
24
43
  /** @type {Record<string, string>} */
25
- const result = {}
44
+ const parsed = {}
26
45
  for (const processedEnv of processedEnvs) {
27
46
  // parsed means we saw the key in a file or --env flag. this effectively filters out any preset machine envs - while still respecting complex evaluating, expansion, and overload. in other words, the value might be the machine value because the key was displayed in a .env file
28
47
  if (processedEnv.parsed) {
29
48
  for (const key of Object.keys(processedEnv.parsed)) {
30
- result[key] = processEnv[key]
49
+ parsed[key] = processEnv[key]
31
50
  }
32
51
  }
33
52
  }
34
53
 
35
- return result
54
+ return { parsed, errors }
36
55
  }
37
-
38
- return processEnv[this.key]
39
56
  }
40
57
  }
41
58
 
@@ -8,9 +8,11 @@ const TYPE_ENV_VAULT_FILE = 'envVaultFile'
8
8
 
9
9
  const decrypt = require('./../helpers/decrypt')
10
10
  const Parse = require('./../helpers/parse')
11
+ const Errors = require('./../helpers/errors')
11
12
  const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
12
13
  const detectEncoding = require('./../helpers/detectEncoding')
13
14
  const findPrivateKey = require('./../helpers/findPrivateKey')
15
+ const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
14
16
  const determineEnvs = require('./../helpers/determineEnvs')
15
17
 
16
18
  class Run {
@@ -59,9 +61,9 @@ class Run {
59
61
  row.string = env
60
62
 
61
63
  try {
62
- const { parsed, warnings, injected, preExisted } = new Parse(env, null, this.processEnv, this.overload).run()
64
+ const { parsed, errors, injected, preExisted } = new Parse(env, null, this.processEnv, this.overload).run()
63
65
  row.parsed = parsed
64
- row.warnings = warnings
66
+ row.errors = errors
65
67
  row.injected = injected
66
68
  row.preExisted = preExisted
67
69
 
@@ -73,7 +75,7 @@ class Run {
73
75
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
74
76
  }
75
77
  } catch (e) {
76
- row.error = e
78
+ row.errors = [e]
77
79
  }
78
80
 
79
81
  this.processedEnvs.push(row)
@@ -91,10 +93,11 @@ class Run {
91
93
  this.readableFilepaths.add(envFilepath)
92
94
 
93
95
  const privateKey = findPrivateKey(envFilepath)
94
- const { parsed, warnings, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload).run()
96
+ const privateKeyName = guessPrivateKeyName(envFilepath)
97
+ const { parsed, errors, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload, privateKeyName).run()
95
98
 
96
99
  row.parsed = parsed
97
- row.warnings = warnings
100
+ row.errors = errors
98
101
  row.injected = injected
99
102
  row.preExisted = preExisted
100
103
 
@@ -104,13 +107,10 @@ class Run {
104
107
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
105
108
  }
106
109
  } catch (e) {
107
- if (e.code === 'ENOENT') {
108
- const error = new Error(`missing ${envFilepath} file (${filepath})`)
109
- error.code = 'MISSING_ENV_FILE'
110
-
111
- row.error = error
110
+ if (e.code === 'ENOENT' || e.code === 'EISDIR') {
111
+ row.errors = [new Errors({ envFilepath, filepath }).missingEnvFile()]
112
112
  } else {
113
- row.error = e
113
+ row.errors = [e]
114
114
  }
115
115
  }
116
116
 
@@ -162,9 +162,9 @@ class Run {
162
162
 
163
163
  try {
164
164
  // parse this. it's the equivalent of the .env file
165
- const { parsed, warnings, injected, preExisted } = new Parse(decrypted, null, this.processEnv, this.overload).run()
165
+ const { parsed, errors, injected, preExisted } = new Parse(decrypted, null, this.processEnv, this.overload).run()
166
166
  row.parsed = parsed
167
- row.warnings = warnings
167
+ row.errors = errors
168
168
  row.injected = injected
169
169
  row.preExisted = preExisted
170
170
 
@@ -174,7 +174,7 @@ class Run {
174
174
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
175
175
  }
176
176
  } catch (e) {
177
- row.error = e
177
+ row.errors = [e]
178
178
  }
179
179
 
180
180
  this.processedEnvs.push(row)
@@ -4,10 +4,11 @@ const dotenv = require('dotenv')
4
4
 
5
5
  const TYPE_ENV_FILE = 'envFile'
6
6
 
7
+ const Errors = require('./../helpers/errors')
7
8
  const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
8
9
  const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
9
10
  const encryptValue = require('./../helpers/encryptValue')
10
- const decryptValue = require('./../helpers/decryptValue')
11
+ const decryptKeyValue = require('./../helpers/decryptKeyValue')
11
12
  const replace = require('./../helpers/replace')
12
13
  const detectEncoding = require('./../helpers/detectEncoding')
13
14
  const determineEnvs = require('./../helpers/determineEnvs')
@@ -84,7 +85,7 @@ class Sets {
84
85
  privateKey = kp.privateKey
85
86
 
86
87
  if (row.originalValue) {
87
- row.originalValue = decryptValue(row.originalValue, privateKey)
88
+ row.originalValue = decryptKeyValue(row.key, row.originalValue, privateKeyName, privateKey)
88
89
  }
89
90
 
90
91
  // if derivation doesn't match what's in the file (or preset in env)
@@ -169,10 +170,7 @@ class Sets {
169
170
  }
170
171
  } catch (e) {
171
172
  if (e.code === 'ENOENT') {
172
- const error = new Error(`missing ${envFilepath} file (${filepath})`)
173
- error.code = 'MISSING_ENV_FILE'
174
-
175
- row.error = error
173
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
176
174
  } else {
177
175
  row.error = e
178
176
  }