@dotenvx/dotenvx 1.24.4 → 1.25.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,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.4...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.25.0...main)
6
+
7
+ ## [1.25.0](https://github.com/dotenvx/dotenvx/compare/v1.24.5...v1.25.0)
8
+
9
+ ### Added
10
+
11
+ * 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))
12
+ * 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))
13
+ * add `strict` option to `config()` to throw for any errors ([#459](https://github.com/dotenvx/dotenvx/pull/459))
14
+
15
+ ### Changed
16
+
17
+ * log `MISSING_ENV_FILE` and `DECRYPTION_FAILED` errors to stderr (prior was stdout as a warning) ([#459](https://github.com/dotenvx/dotenvx/pull/459))
18
+
19
+ ### Removed
20
+
21
+ * remove `dotenvx.get()` function from `lib/main.js`. (`parse` already historically exists for this purpose) ([#461](https://github.com/dotenvx/dotenvx/pull/461))
22
+
23
+ ## [1.24.5](https://github.com/dotenvx/dotenvx/compare/v1.24.4...v1.24.5)
24
+
25
+ ### Changed
26
+
27
+ * 🐞 do not expand prior literal values ([#458](https://github.com/dotenvx/dotenvx/pull/458))
6
28
 
7
29
  ## [1.24.4](https://github.com/dotenvx/dotenvx/compare/v1.24.3...v1.24.4)
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
 
@@ -1607,6 +1632,110 @@ More examples
1607
1632
 
1608
1633
  </details>
1609
1634
 
1635
+ ### config() 📦
1636
+
1637
+ * <details><summary>`config()`</summary><br>
1638
+
1639
+ Use directly in node.js code.
1640
+
1641
+ ```ini
1642
+ # .env
1643
+ HELLO="World"
1644
+ ```
1645
+
1646
+ ```js
1647
+ // index.js
1648
+ require('@dotenvx/dotenvx').config()
1649
+
1650
+ console.log(`Hello ${process.env.HELLO}`)
1651
+ ```
1652
+
1653
+ ```sh
1654
+ $ node index.js
1655
+ [dotenvx@1.24.5] injecting env (1) from .env
1656
+ Hello World
1657
+ ```
1658
+
1659
+ </details>
1660
+ * <details><summary>`config(path: ['.env.local', '.env'])` - multiple files</summary><br>
1661
+
1662
+ Specify path(s) to multiple .env files.
1663
+
1664
+ ```ini
1665
+ # .env.local
1666
+ HELLO="Me"
1667
+ ```
1668
+
1669
+ ```ini
1670
+ # .env
1671
+ HELLO="World"
1672
+ ```
1673
+
1674
+ ```js
1675
+ // index.js
1676
+ require('@dotenvx/dotenvx').config({path: ['.env.local', '.env']})
1677
+
1678
+ console.log(`Hello ${process.env.HELLO}`)
1679
+ ```
1680
+
1681
+ ```sh
1682
+ $ node index.js
1683
+ [dotenvx@1.24.5] injecting env (1) from .env.local, .env
1684
+ Hello Me
1685
+ ```
1686
+
1687
+ </details>
1688
+ * <details><summary>`config(overload: true)` - overload</summary><br>
1689
+
1690
+ User `overload` to overwrite the prior set value.
1691
+
1692
+ ```ini
1693
+ # .env.local
1694
+ HELLO="Me"
1695
+ ```
1696
+
1697
+ ```ini
1698
+ # .env
1699
+ HELLO="World"
1700
+ ```
1701
+
1702
+ ```js
1703
+ // index.js
1704
+ require('@dotenvx/dotenvx').config({path: ['.env.local', '.env'], overload: true})
1705
+
1706
+ console.log(`Hello ${process.env.HELLO}`)
1707
+ ```
1708
+
1709
+ ```sh
1710
+ $ node index.js
1711
+ [dotenvx@1.24.5] injecting env (1) from .env.local, .env
1712
+ Hello World
1713
+ ```
1714
+
1715
+ </details>
1716
+ * <details><summary>`config(strict: true)` - strict</summary><br>
1717
+
1718
+ Use `strict` to throw if an error is encountered - like a missing .env file.
1719
+
1720
+ ```ini
1721
+ # .env
1722
+ HELLO="World"
1723
+ ```
1724
+
1725
+ ```js
1726
+ // index.js
1727
+ require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], strict: true})
1728
+
1729
+ console.log(`Hello ${process.env.HELLO}`)
1730
+ ```
1731
+
1732
+ ```sh
1733
+ $ node index.js
1734
+ Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
1735
+ ```
1736
+
1737
+ </details>
1738
+
1610
1739
  ### Extensions 🔌
1611
1740
 
1612
1741
  * <details><summary>`ext genexample`</summary><br>
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.24.4",
2
+ "version": "1.25.0",
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
  }
@@ -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,30 @@
1
+ class Errors {
2
+ constructor (options = {}) {
3
+ this.filepath = options.filepath
4
+ this.envFilepath = options.envFilepath
5
+
6
+ this.key = options.key
7
+ }
8
+
9
+ missingEnvFile () {
10
+ const code = 'MISSING_ENV_FILE'
11
+ const message = `[${code}] missing ${this.envFilepath} file (${this.filepath})`
12
+ const help = `[${code}] ? add one with [echo "HELLO=World" > ${this.envFilepath}]`
13
+
14
+ const e = new Error(message)
15
+ e.code = code
16
+ e.help = help
17
+ return e
18
+ }
19
+
20
+ missingKey () {
21
+ const code = 'MISSING_KEY'
22
+ const message = `[${code}] missing ${this.key} key`
23
+
24
+ const e = new Error(message)
25
+ e.code = code
26
+ return e
27
+ }
28
+ }
29
+
30
+ module.exports = Errors
@@ -16,10 +16,12 @@ class Parse {
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 = {}
23
+ // for use with stopping expansion for literals
24
+ this.literals = {}
23
25
  }
24
26
 
25
27
  run () {
@@ -40,7 +42,7 @@ class Parse {
40
42
  try {
41
43
  this.parsed[key] = this.decrypt(this.parsed[key])
42
44
  } catch (e) {
43
- this.warnings.push(this.warning(e, key))
45
+ this.errors.push(this.error(e, key))
44
46
  }
45
47
 
46
48
  // eval empty, double, or backticks
@@ -58,6 +60,10 @@ class Parse {
58
60
  this.parsed[key] = resolveEscapeSequences(this.expand(this.parsed[key]))
59
61
  }
60
62
 
63
+ if (quote === "'") {
64
+ this.literals[key] = this.parsed[key]
65
+ }
66
+
61
67
  // for use with progressive expansion
62
68
  this.runningParsed[key] = this.parsed[key]
63
69
 
@@ -72,8 +78,8 @@ class Parse {
72
78
  parsed: this.parsed,
73
79
  processEnv: this.processEnv,
74
80
  injected: this.injected,
75
- warnings: this.warnings,
76
- preExisted: this.preExisted
81
+ preExisted: this.preExisted,
82
+ errors: this.errors
77
83
  }
78
84
  }
79
85
 
@@ -162,7 +168,6 @@ class Parse {
162
168
 
163
169
  let defaultValue
164
170
  let value
165
-
166
171
  const key = r.shift()
167
172
 
168
173
  if ([':+', '+'].includes(splitter)) {
@@ -184,6 +189,11 @@ class Parse {
184
189
  break
185
190
  }
186
191
 
192
+ // if the result came from what was a literal value then stop expanding
193
+ if (this.literals[key]) {
194
+ break
195
+ }
196
+
187
197
  regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
188
198
  }
189
199
 
@@ -198,12 +208,12 @@ class Parse {
198
208
  return (this.src || '').toString().replace(/\r\n?/mg, '\n') // Convert buffer to string and Convert line breaks to same format
199
209
  }
200
210
 
201
- warning (e, key) {
202
- const warning = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(this.privateKey)}'`)
203
- warning.code = e.code
204
- warning.help = `[${e.code}] ? ${e.message}`
211
+ error (e, key) {
212
+ const error = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(this.privateKey)}'`)
213
+ error.code = e.code
214
+ error.help = `[${e.code}] ? ${e.message}`
205
215
 
206
- return warning
216
+ return error
207
217
  }
208
218
  }
209
219
 
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,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 findPrivateKey = require('./../helpers/findPrivateKey')
10
11
  const decryptValue = require('./../helpers/decryptValue')
@@ -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,6 +8,7 @@ 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')
@@ -59,9 +60,9 @@ class Run {
59
60
  row.string = env
60
61
 
61
62
  try {
62
- const { parsed, warnings, injected, preExisted } = new Parse(env, null, this.processEnv, this.overload).run()
63
+ const { parsed, errors, injected, preExisted } = new Parse(env, null, this.processEnv, this.overload).run()
63
64
  row.parsed = parsed
64
- row.warnings = warnings
65
+ row.errors = errors
65
66
  row.injected = injected
66
67
  row.preExisted = preExisted
67
68
 
@@ -73,7 +74,7 @@ class Run {
73
74
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
74
75
  }
75
76
  } catch (e) {
76
- row.error = e
77
+ row.errors = [e]
77
78
  }
78
79
 
79
80
  this.processedEnvs.push(row)
@@ -91,10 +92,10 @@ class Run {
91
92
  this.readableFilepaths.add(envFilepath)
92
93
 
93
94
  const privateKey = findPrivateKey(envFilepath)
94
- const { parsed, warnings, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload).run()
95
+ const { parsed, errors, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload).run()
95
96
 
96
97
  row.parsed = parsed
97
- row.warnings = warnings
98
+ row.errors = errors
98
99
  row.injected = injected
99
100
  row.preExisted = preExisted
100
101
 
@@ -104,13 +105,10 @@ class Run {
104
105
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
105
106
  }
106
107
  } 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
108
+ if (e.code === 'ENOENT' || e.code === 'EISDIR') {
109
+ row.errors = [new Errors({ envFilepath, filepath }).missingEnvFile()]
112
110
  } else {
113
- row.error = e
111
+ row.errors = [e]
114
112
  }
115
113
  }
116
114
 
@@ -162,9 +160,9 @@ class Run {
162
160
 
163
161
  try {
164
162
  // 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()
163
+ const { parsed, errors, injected, preExisted } = new Parse(decrypted, null, this.processEnv, this.overload).run()
166
164
  row.parsed = parsed
167
- row.warnings = warnings
165
+ row.errors = errors
168
166
  row.injected = injected
169
167
  row.preExisted = preExisted
170
168
 
@@ -174,7 +172,7 @@ class Run {
174
172
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
175
173
  }
176
174
  } catch (e) {
177
- row.error = e
175
+ row.errors = [e]
178
176
  }
179
177
 
180
178
  this.processedEnvs.push(row)
@@ -4,6 +4,7 @@ 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')
@@ -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
  }