@dotenvx/dotenvx 1.22.2 → 1.24.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,37 @@
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.22.2...main)
5
+ ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.24.0...main)
6
+
7
+ ## 1.24.0
8
+
9
+ ### Added
10
+
11
+ * support progressive append/update ([#445](https://github.com/dotenvx/dotenvx/pull/445))
12
+
13
+ ```ini
14
+ FOO=foo
15
+ FOO=${FOO}bar
16
+ # foobar
17
+ ```
18
+
19
+ * support alternate value expansion ([#445](https://github.com/dotenvx/dotenvx/pull/445))
20
+
21
+ <img width="1608" alt="image" src="https://github.com/user-attachments/assets/fdd55a0a-9b36-4cb3-b0c6-6b019441aef4">
22
+
23
+ ### Changed
24
+
25
+ * `dotenvx.parse` now maps to dotenvx's internal parser. (prior it was mapping to [dotenv's](https://github.com/motdotla/dotenv))
26
+
27
+ ### Removed
28
+
29
+ * removed `dotenvx.configDotenv()`. use `dotenvx.config()` ([#445](https://github.com/dotenvx/dotenvx/pull/445))
30
+
31
+ ## 1.23.0
32
+
33
+ ### Added
34
+
35
+ * deeper variable expansion support and protection against self-referencing variables 🛡️ ([#439](https://github.com/dotenvx/dotenvx/pull/439))
6
36
 
7
37
  ## 1.22.2
8
38
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.22.2",
2
+ "version": "1.24.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -31,8 +31,7 @@
31
31
  "test-single": "tap run --coverage-report=none tests/cli/actions/decrypt.test.js",
32
32
  "testshell": "bash shellspec",
33
33
  "prerelease": "npm test && npm run testshell",
34
- "release": "standard-version",
35
- "patch": "patch-package"
34
+ "release": "standard-version"
36
35
  },
37
36
  "funding": "https://dotenvx.com",
38
37
  "dependencies": {
@@ -47,9 +46,8 @@
47
46
  "which": "^4.0.0"
48
47
  },
49
48
  "devDependencies": {
49
+ "@yao-pkg/pkg": "^5.14.2",
50
50
  "capture-console": "^1.0.2",
51
- "patch-package": "^8.0.0",
52
- "pkg": "^5.8.1",
53
51
  "proxyquire": "^2.1.3",
54
52
  "sinon": "^14.0.1",
55
53
  "standard": "^17.1.0",
@@ -0,0 +1,5 @@
1
+ function chomp (value) {
2
+ return value.replace(/[\r\n]+$/, '')
3
+ }
4
+
5
+ module.exports = chomp
@@ -1,8 +1,11 @@
1
1
  const execa = require('execa')
2
+ /* c8 ignore start */
3
+ const pkgArgs = process.pkg ? { PKG_EXECPATH: '' } : {}
4
+ /* c8 ignore stop */
2
5
 
3
6
  const execute = {
4
7
  execa (command, args, options) {
5
- return execa(command, args, options)
8
+ return execa(command, args, { ...options, env: { ...options.env, ...pkgArgs } })
6
9
  }
7
10
  }
8
11
 
@@ -0,0 +1,208 @@
1
+ const chomp = require('./chomp')
2
+ const truncate = require('./truncate')
3
+ const decryptValue = require('./decryptValue')
4
+ const resolveEscapeSequences = require('./resolveEscapeSequences')
5
+ const { execSync } = require('child_process')
6
+
7
+ class Parse {
8
+ static LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
9
+
10
+ constructor (src, privateKey = null, processEnv = process.env, overload = false) {
11
+ this.src = src
12
+ this.privateKey = privateKey
13
+ this.processEnv = processEnv
14
+ this.overload = overload
15
+
16
+ this.parsed = {}
17
+ this.preExisted = {}
18
+ this.injected = {}
19
+ this.warnings = []
20
+
21
+ // for use with progressive expansion
22
+ this.runningParsed = {}
23
+ }
24
+
25
+ run () {
26
+ const lines = this.getLines()
27
+
28
+ let match
29
+ while ((match = Parse.LINE.exec(lines)) !== null) {
30
+ const key = match[1]
31
+ const value = match[2]
32
+ const quote = this.quote(value) // must be raw match
33
+ this.parsed[key] = this.clean(value, quote) // file value
34
+
35
+ if (!this.overload && this.inProcessEnv(key)) {
36
+ this.parsed[key] = this.processEnv[key] // use process.env pre-existing value
37
+ }
38
+
39
+ // decrypt
40
+ try {
41
+ this.parsed[key] = this.decrypt(this.parsed[key])
42
+ } catch (e) {
43
+ this.warnings.push(this.warning(e, key))
44
+ }
45
+
46
+ // eval empty, double, or backticks
47
+ if (quote !== "'" && (!this.inProcessEnv(key) || this.processEnv[key] === this.parsed[key])) {
48
+ this.parsed[key] = this.eval(this.parsed[key])
49
+ }
50
+
51
+ // expand empty, double, or backticks
52
+ if (quote !== "'") {
53
+ this.parsed[key] = resolveEscapeSequences(this.expand(this.parsed[key]))
54
+ }
55
+
56
+ // for use with progressive expansion
57
+ this.runningParsed[key] = this.parsed[key]
58
+
59
+ if (Object.prototype.hasOwnProperty.call(this.processEnv, key) && !this.overload) {
60
+ this.preExisted[key] = this.processEnv[key] // track preExisted
61
+ } else {
62
+ this.injected[key] = this.parsed[key] // track injected
63
+ }
64
+ }
65
+
66
+ return {
67
+ parsed: this.parsed,
68
+ processEnv: this.processEnv,
69
+ injected: this.injected,
70
+ warnings: this.warnings,
71
+ preExisted: this.preExisted
72
+ }
73
+ }
74
+
75
+ trimmer (value) {
76
+ // Default undefined or null to empty string
77
+ return (value || '').trim()
78
+ }
79
+
80
+ quote (value) {
81
+ const v = this.trimmer(value)
82
+ const maybeQuote = v[0]
83
+ let q = ''
84
+ switch (maybeQuote) {
85
+ // single
86
+ case "'":
87
+ q = "'"
88
+ break
89
+ // double
90
+ case '"':
91
+ q = '"'
92
+ break
93
+ // backtick
94
+ case '`':
95
+ q = '`'
96
+ break
97
+ // empty
98
+ default:
99
+ q = ''
100
+ }
101
+
102
+ return q
103
+ }
104
+
105
+ clean (value, _quote) {
106
+ let v = this.trimmer(value)
107
+
108
+ // Remove surrounding quotes
109
+ v = v.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
110
+
111
+ // Expand newlines if double quoted
112
+ if (_quote === '"') {
113
+ v = v.replace(/\\n/g, '\n')
114
+ v = v.replace(/\\r/g, '\r')
115
+ }
116
+
117
+ return v
118
+ }
119
+
120
+ decrypt (value) {
121
+ return decryptValue(value, this.privateKey)
122
+ }
123
+
124
+ eval (value) {
125
+ const matches = value.match(/\$\([^()]+\)/) || []
126
+
127
+ return matches.reduce(function (newValue, match) {
128
+ const command = match.substring(2, match.length - 1) // get command
129
+ const value = chomp(execSync(command).toString()) // execute command
130
+ return newValue.replace(match, value) // replace with command value
131
+ }, value)
132
+ }
133
+
134
+ expand (value) {
135
+ let env = { ...this.runningParsed, ...this.processEnv } // typically process.env wins
136
+ if (this.overload) {
137
+ env = { ...this.processEnv, ...this.runningParsed } // parsed wins
138
+ }
139
+
140
+ const regex = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
141
+
142
+ let result = value
143
+ let match
144
+ const seen = new Set() // self-referential checker
145
+
146
+ while ((match = regex.exec(result)) !== null) {
147
+ seen.add(result)
148
+
149
+ const [template, bracedExpression, unbracedExpression] = match
150
+ const expression = bracedExpression || unbracedExpression
151
+
152
+ // match the operators `:+`, `+`, `:-`, and `-`
153
+ const opRegex = /(:\+|\+|:-|-)/
154
+ // find first match
155
+ const opMatch = expression.match(opRegex)
156
+ const splitter = opMatch ? opMatch[0] : null
157
+
158
+ const r = expression.split(splitter)
159
+
160
+ let key
161
+ let defaultValue
162
+ let value
163
+
164
+ if ([':+', '+'].includes(splitter)) {
165
+ key = r.shift()
166
+ defaultValue = env[key] ? r.join(splitter) : ''
167
+ value = null
168
+ } else {
169
+ key = r.shift()
170
+ defaultValue = r.join(splitter)
171
+ value = env[key]
172
+ }
173
+
174
+ if (value) {
175
+ // self-referential check
176
+ if (seen.has(value)) {
177
+ result = result.replace(template, defaultValue)
178
+ } else {
179
+ result = result.replace(template, value)
180
+ }
181
+ } else {
182
+ result = result.replace(template, defaultValue)
183
+ }
184
+
185
+ regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
186
+ }
187
+
188
+ return result
189
+ }
190
+
191
+ inProcessEnv (key) {
192
+ return Object.prototype.hasOwnProperty.call(this.processEnv, key)
193
+ }
194
+
195
+ getLines () {
196
+ return (this.src || '').toString().replace(/\r\n?/mg, '\n') // Convert buffer to string and Convert line breaks to same format
197
+ }
198
+
199
+ warning (e, key) {
200
+ const warning = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(this.privateKey)}'`)
201
+ warning.code = e.code
202
+ warning.help = `[${e.code}] ? ${e.message}`
203
+
204
+ return warning
205
+ }
206
+ }
207
+
208
+ module.exports = Parse
@@ -0,0 +1,5 @@
1
+ function resolveEscapeSequences (value) {
2
+ return value.replace(/\\\$/g, '$')
3
+ }
4
+
5
+ module.exports = resolveEscapeSequences
package/src/lib/main.d.ts CHANGED
@@ -124,17 +124,6 @@ export interface DotenvPopulateInput {
124
124
  */
125
125
  export function config(options?: DotenvConfigOptions): DotenvConfigOutput;
126
126
 
127
- /**
128
- * Loads `.env` file contents into process.env.
129
- *
130
- * @see https://dotenvx.com/docs
131
- *
132
- * @param options - additional options. example: `{ path: './custom/path', encoding: 'latin1', debug: true, override: false }`
133
- * @returns an object with a `parsed` key if successful or `error` key if an error occurred. example: { parsed: { KEY: 'value' } }
134
- *
135
- */
136
- export function configDotenv(options?: DotenvConfigOptions): DotenvConfigOutput;
137
-
138
127
  /**
139
128
  * Decrypt ciphertext
140
129
  *
package/src/lib/main.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // @ts-check
2
2
  const path = require('path')
3
- const dotenv = require('dotenv')
4
3
 
5
4
  // shared
6
5
  const { setLogLevel, logger } = require('./../shared/logger')
@@ -16,8 +15,7 @@ const Genexample = require('./services/genexample')
16
15
  // helpers
17
16
  const conventions = require('./helpers/conventions')
18
17
  const dotenvOptionPaths = require('./helpers/dotenvOptionPaths')
19
-
20
- // proxies to dotenv
18
+ const Parse = require('./helpers/parse')
21
19
 
22
20
  /** @type {import('./main').config} */
23
21
  const config = function (options = {}) {
@@ -162,14 +160,23 @@ const config = function (options = {}) {
162
160
  }
163
161
  }
164
162
 
165
- /** @type {import('./main').configDotenv} */
166
- const configDotenv = function (options) {
167
- return dotenv.configDotenv(options)
168
- }
169
-
170
163
  /** @type {import('./main').parse} */
171
- const parse = function (src) {
172
- return dotenv.parse(src)
164
+ const parse = function (src, options = {}) {
165
+ // allow user to set processEnv to read from
166
+ let processEnv = process.env
167
+ if (options && options.processEnv != null) {
168
+ processEnv = options.processEnv
169
+ }
170
+
171
+ // private decryption key
172
+ const privateKey = null // implement later
173
+
174
+ // overload
175
+ const overload = options.overload || options.override
176
+
177
+ const { parsed } = new Parse(src, privateKey, processEnv, overload).run()
178
+
179
+ return parsed
173
180
  }
174
181
 
175
182
  /** @type {import('./main').ls} */
@@ -201,7 +208,6 @@ const keypair = function (envFile, key) {
201
208
  module.exports = {
202
209
  // dotenv proxies
203
210
  config,
204
- configDotenv,
205
211
  parse,
206
212
  // actions related
207
213
  ls,
@@ -6,9 +6,8 @@ const TYPE_ENV = 'env'
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
  const TYPE_ENV_VAULT_FILE = 'envVaultFile'
8
8
 
9
- const inject = require('./../helpers/inject')
10
9
  const decrypt = require('./../helpers/decrypt')
11
- const parseDecryptEvalExpand = require('./../helpers/parseDecryptEvalExpand')
10
+ const Parse = require('./../helpers/parse')
12
11
  const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
13
12
  const detectEncoding = require('./../helpers/detectEncoding')
14
13
  const findPrivateKey = require('./../helpers/findPrivateKey')
@@ -60,15 +59,16 @@ class Run {
60
59
  row.string = env
61
60
 
62
61
  try {
63
- const { parsed, processEnv, warnings } = parseDecryptEvalExpand(env, null, this.processEnv)
62
+ const { parsed, warnings, injected, preExisted } = new Parse(env, null, this.processEnv, this.overload).run()
64
63
  row.parsed = parsed
65
64
  row.warnings = warnings
66
- this.readableStrings.add(env)
67
-
68
- const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
69
65
  row.injected = injected
70
66
  row.preExisted = preExisted
71
67
 
68
+ this.inject(row.parsed) // inject
69
+
70
+ this.readableStrings.add(env)
71
+
72
72
  for (const key of Object.keys(injected)) {
73
73
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
74
74
  }
@@ -91,14 +91,15 @@ class Run {
91
91
  this.readableFilepaths.add(envFilepath)
92
92
 
93
93
  const privateKey = findPrivateKey(envFilepath)
94
- const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
94
+ const { parsed, warnings, injected, preExisted } = new Parse(src, privateKey, this.processEnv, this.overload).run()
95
+
95
96
  row.parsed = parsed
96
97
  row.warnings = warnings
97
-
98
- const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
99
98
  row.injected = injected
100
99
  row.preExisted = preExisted
101
100
 
101
+ this.inject(row.parsed) // inject
102
+
102
103
  for (const key of Object.keys(injected)) {
103
104
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
104
105
  }
@@ -161,14 +162,14 @@ class Run {
161
162
 
162
163
  try {
163
164
  // parse this. it's the equivalent of the .env file
164
- const { parsed, processEnv, warnings } = parseDecryptEvalExpand(decrypted, null, this.processEnv)
165
+ const { parsed, warnings, injected, preExisted } = new Parse(decrypted, null, this.processEnv, this.overload).run()
165
166
  row.parsed = parsed
166
167
  row.warnings = warnings
167
-
168
- const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
169
168
  row.injected = injected
170
169
  row.preExisted = preExisted
171
170
 
171
+ this.inject(row.parsed) // inject
172
+
172
173
  for (const key of Object.keys(injected)) {
173
174
  this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
174
175
  }
@@ -179,8 +180,10 @@ class Run {
179
180
  this.processedEnvs.push(row)
180
181
  }
181
182
 
182
- _inject (clonedProcessEnv, parsed, overload, processEnv) {
183
- return inject(clonedProcessEnv, parsed, overload, processEnv)
183
+ inject (parsed) {
184
+ for (const key of Object.keys(parsed)) {
185
+ this.processEnv[key] = parsed[key] // inject to process.env
186
+ }
184
187
  }
185
188
 
186
189
  // handle scenario for comma separated keys - for use with key rotation
@@ -1,54 +0,0 @@
1
- const { execSync } = require('child_process')
2
-
3
- function _chomp (value) {
4
- return value.replace(/[\r\n]+$/, '')
5
- }
6
-
7
- function interpolate (value, processEnv, parsed) {
8
- const matches = value.match(/\$\([^()]+\)/) || []
9
-
10
- return matches.reduce(function (newValue, match) {
11
- // get command
12
- const command = match.substring(2, match.length - 1)
13
- // execute command
14
- const value = _chomp(execSync(command).toString())
15
- // replace with command value
16
- return newValue.replace(match, value)
17
- }, value)
18
- }
19
-
20
- function evaluate (options) {
21
- let processEnv = process.env
22
- if (options && options.processEnv != null) {
23
- processEnv = options.processEnv
24
- }
25
-
26
- for (const key in options.parsed) {
27
- let value = options.parsed[key]
28
-
29
- const inProcessEnv = Object.prototype.hasOwnProperty.call(processEnv, key)
30
-
31
- if (inProcessEnv) {
32
- if (processEnv[key] === options.parsed[key]) {
33
- // assume was set to processEnv from the .env file if the values match and therefore interpolate
34
- value = interpolate(value, processEnv, options.parsed)
35
- } else {
36
- // do not interpolate - assume processEnv had the intended value even if containing a $.
37
- value = processEnv[key]
38
- }
39
- } else {
40
- // not inProcessEnv so assume interpolation for this .env key
41
- value = interpolate(value, processEnv, options.parsed)
42
- }
43
-
44
- options.parsed[key] = value
45
- }
46
-
47
- for (const processKey in options.parsed) {
48
- processEnv[processKey] = options.parsed[processKey]
49
- }
50
-
51
- return options
52
- }
53
-
54
- module.exports.eval = evaluate
@@ -1,78 +0,0 @@
1
- // * /
2
- // * (\\)? # is it escaped with a backslash?
3
- // * (\$) # literal $
4
- // * (?!\() # shouldnt be followed by parenthesis
5
- // * (\{?) # first brace wrap opening
6
- // * ([\w.]+) # key
7
- // * (?::-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))? # optional default nested 3 times
8
- // * (\}?) # last brace warp closing
9
- // * /xi
10
-
11
- const DOTENV_SUBSTITUTION_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi
12
-
13
- function _resolveEscapeSequences (value) {
14
- return value.replace(/\\\$/g, '$')
15
- }
16
-
17
- function interpolate (value, lookups) {
18
- return value.replace(DOTENV_SUBSTITUTION_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue, closeBrace) => {
19
- if (escaped === '\\') {
20
- return match.slice(1)
21
- } else {
22
- if (lookups[key]) {
23
- // avoid recursion from EXPAND_SELF=$EXPAND_SELF
24
- if (lookups[key] === value) {
25
- return lookups[key]
26
- } else {
27
- return interpolate(lookups[key], lookups)
28
- }
29
- }
30
-
31
- if (defaultValue) {
32
- if (defaultValue.startsWith('$')) {
33
- return interpolate(defaultValue, lookups)
34
- } else {
35
- return defaultValue
36
- }
37
- }
38
-
39
- return ''
40
- }
41
- })
42
- }
43
-
44
- function expand (options) {
45
- const processEnv = options.processEnv || {}
46
- const parsed = options.parsed || {}
47
-
48
- const combined = { ...processEnv, ...parsed }
49
- const combinedReversed = { ...parsed, ...processEnv }
50
-
51
- for (const key in parsed) {
52
- const value = parsed[key]
53
-
54
- // interpolate using both file and processEnv (file interpolation wins. used for --overload later)
55
- const fileValue = _resolveEscapeSequences(interpolate(value, combined))
56
- parsed[key] = fileValue
57
-
58
- if (fileValue === _resolveEscapeSequences(value)) {
59
- continue // no change means no expansion, move on
60
- }
61
-
62
- if (processEnv[key]) {
63
- continue // already has a value in processEnv, move on
64
- }
65
-
66
- const processEnvValue = interpolate(value, combinedReversed) // could be empty string ''
67
- if (processEnvValue) {
68
- processEnv[key] = _resolveEscapeSequences(processEnvValue) // set it
69
- }
70
- }
71
-
72
- return {
73
- parsed,
74
- processEnv
75
- }
76
- }
77
-
78
- module.exports.expand = expand
@@ -1,27 +0,0 @@
1
- function inject (clonedProcessEnv = {}, parsed = {}, overload = false, processEnv = process.env) {
2
- const injected = {}
3
- const preExisted = {}
4
-
5
- // set processEnv
6
- for (const key of Object.keys(parsed)) {
7
- if (Object.prototype.hasOwnProperty.call(clonedProcessEnv, key)) {
8
- if (overload === true) {
9
- processEnv[key] = parsed[key]
10
- injected[key] = parsed[key] // track injected key/value
11
- } else {
12
- processEnv[key] = clonedProcessEnv[key]
13
- preExisted[key] = clonedProcessEnv[key] // track preExisted key/value
14
- }
15
- } else {
16
- processEnv[key] = parsed[key]
17
- injected[key] = parsed[key] // track injected key/value
18
- }
19
- }
20
-
21
- return {
22
- injected,
23
- preExisted
24
- }
25
- }
26
-
27
- module.exports = inject
@@ -1,84 +0,0 @@
1
- const dotenv = require('dotenv')
2
- const dotenvEval = require('./dotenvEval')
3
- const dotenvExpand = require('./dotenvExpand')
4
- const decryptValue = require('./decryptValue')
5
- const truncate = require('./truncate')
6
- const quotes = require('./quotes')
7
-
8
- function warning (e, key, privateKey = null) {
9
- const warning = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(privateKey)}'`)
10
- warning.code = e.code
11
- warning.help = `[${e.code}] ? ${e.message}`
12
-
13
- return warning
14
- }
15
-
16
- function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.env) {
17
- const warnings = []
18
-
19
- // parse and quotes
20
- const parsed = dotenv.parse(src)
21
- const _quotes = quotes(src)
22
- const originalParsed = { ...parsed }
23
- const originalProcessEnv = { ...processEnv }
24
- for (const key in parsed) {
25
- try {
26
- const decryptedValue = decryptValue(parsed[key], privateKey)
27
- parsed[key] = decryptedValue
28
- } catch (_e) {
29
- // do nothing. warnings tracked further below.
30
- }
31
- }
32
-
33
- // eval parsed only. do NOT eval process.env ever. too risky/dangerous.
34
- const inputParsed = {
35
- processEnv: {},
36
- parsed
37
- }
38
- const evaled = dotenvEval.eval(inputParsed).parsed
39
-
40
- // expanded
41
- const inputEvaled = {
42
- processEnv,
43
- parsed: evaled
44
- }
45
- const expanded = dotenvExpand.expand(inputEvaled)
46
-
47
- for (const key in expanded.parsed) {
48
- // unset eval and expansion for single quotes
49
- if (_quotes[key] === "'") {
50
- expanded.parsed[key] = originalParsed[key] // reset to original
51
- }
52
-
53
- try {
54
- const decryptedValue = decryptValue(expanded.parsed[key], privateKey)
55
- expanded.parsed[key] = decryptedValue
56
- } catch (e) {
57
- warnings.push(warning(e, key, privateKey))
58
- }
59
- }
60
-
61
- for (const key in processEnv) {
62
- // unset eval and expansion for single quotes
63
- if (_quotes[key] === "'") {
64
- processEnv[key] = originalProcessEnv[key] || originalParsed[key] // reset to original
65
- }
66
-
67
- try {
68
- const decryptedValue = decryptValue(processEnv[key], privateKey)
69
- processEnv[key] = decryptedValue
70
- } catch (e) {
71
- warnings.push(warning(e, key, privateKey))
72
- }
73
- }
74
-
75
- // for logging only log the original keys existing in parsed. this feels unnecessarily complex - like dotenv-expand should support the ability to inject additional `process.env` or objects as it sees fit to the object it wants to expand
76
- const result = {}
77
- for (const key in parsed) {
78
- result[key] = expanded.parsed[key]
79
- }
80
-
81
- return { parsed: result, processEnv, warnings }
82
- }
83
-
84
- module.exports = parseDecryptEvalExpand