@dotenvx/dotenvx 0.20.1 → 0.20.2

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/README.md CHANGED
@@ -162,8 +162,8 @@ More examples
162
162
  ```sh
163
163
  $ echo "HELLO=World" > .env
164
164
 
165
- $ dotenvx run --quiet -- sh -c 'echo $HELLO'
166
- World
165
+ $ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
166
+ Hello World
167
167
  ```
168
168
 
169
169
  </details>
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.20.1",
2
+ "version": "0.20.2",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -1,14 +1,10 @@
1
- const fs = require('fs')
2
1
  const path = require('path')
3
2
  const execa = require('execa')
4
3
  const logger = require('./../../shared/logger')
5
- const helpers = require('./../helpers')
6
- const main = require('./../../lib/main')
7
- const parseEncryptionKeyFromDotenvKey = require('./../../lib/helpers/parseEncryptionKeyFromDotenvKey')
8
4
 
9
5
  const RunDefault = require('./../../lib/services/runDefault')
6
+ const RunVault = require('./../../lib/services/runVault')
10
7
 
11
- const ENCODING = 'utf8'
12
8
  const REPORT_ISSUE_LINK = 'https://github.com/dotenvx/dotenvx/issues/new'
13
9
 
14
10
  const executeCommand = async function (commandArgs, env) {
@@ -80,31 +76,6 @@ const executeCommand = async function (commandArgs, env) {
80
76
  }
81
77
  }
82
78
 
83
- const _parseCipherTextFromDotenvKeyAndParsedVault = function (dotenvKey, parsedVault) {
84
- // Parse DOTENV_KEY. Format is a URI
85
- let uri
86
- try {
87
- uri = new URL(dotenvKey)
88
- } catch (e) {
89
- throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
90
- }
91
-
92
- // Get environment
93
- const environment = uri.searchParams.get('environment')
94
- if (!environment) {
95
- throw new Error('INVALID_DOTENV_KEY: Missing environment part')
96
- }
97
-
98
- // Get ciphertext payload
99
- const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`
100
- const ciphertext = parsedVault[environmentKey] // DOTENV_VAULT_PRODUCTION
101
- if (!ciphertext) {
102
- throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: cannot locate environment ${environmentKey} in your .env.vault file`)
103
- }
104
-
105
- return ciphertext
106
- }
107
-
108
79
  async function run () {
109
80
  const commandArgs = this.args
110
81
  logger.debug(`process command [${commandArgs.join(' ')}]`)
@@ -114,56 +85,46 @@ async function run () {
114
85
 
115
86
  // load from .env.vault file
116
87
  if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
117
- const envVaultFilepath = options.envVaultFile // .env.vault
118
- const filepath = helpers.resolvePath(envVaultFilepath)
88
+ try {
89
+ const {
90
+ envVaultFile,
91
+ parsed,
92
+ injected,
93
+ preExisted,
94
+ uniqueInjectedKeys
95
+ } = new RunVault(options.envVaultFile, options.env, process.env.DOTENV_KEY, options.overload).run()
96
+
97
+ logger.verbose(`loading env from encrypted ${envVaultFile} (${path.resolve(envVaultFile)})`)
98
+ logger.debug(`decrypting encrypted env from ${envVaultFile} (${path.resolve(envVaultFile)})`)
99
+
100
+ // debug parsed
101
+ logger.debug(parsed)
102
+
103
+ // verbose/debug injected key/value
104
+ for (const [key, value] of Object.entries(injected)) {
105
+ logger.verbose(`${key} set`)
106
+ logger.debug(`${key} set to ${value}`)
107
+ }
119
108
 
120
- if (!fs.existsSync(filepath)) {
121
- logger.error(`you set DOTENV_KEY but your .env.vault file is missing: ${filepath}`)
122
- } else {
123
- logger.verbose(`loading env from encrypted ${filepath}`)
124
-
125
- try {
126
- const src = fs.readFileSync(filepath, { encoding: ENCODING })
127
- const parsedVault = main.parse(src)
128
-
129
- logger.debug(`decrypting encrypted env from ${filepath}`)
130
- // handle scenario for comma separated keys - for use with key rotation
131
- // example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
132
- const dotenvKeys = process.env.DOTENV_KEY.split(',')
133
- const length = dotenvKeys.length
134
-
135
- let decrypted
136
- for (let i = 0; i < length; i++) {
137
- try {
138
- // Get full dotenvKey
139
- const dotenvKey = dotenvKeys[i].trim()
140
-
141
- const key = parseEncryptionKeyFromDotenvKey(dotenvKey)
142
- const ciphertext = _parseCipherTextFromDotenvKeyAndParsedVault(dotenvKey, parsedVault)
143
-
144
- // Decrypt
145
- decrypted = main.decrypt(ciphertext, key)
146
-
147
- break
148
- } catch (error) {
149
- // last key
150
- if (i + 1 >= length) {
151
- throw error
152
- }
153
- // try next key
154
- }
155
- }
156
- logger.debug(decrypted)
157
- const parsed = main.parseExpand(decrypted)
158
- const result = main.inject(process.env, parsed, options.overload)
109
+ // verbose/debug preExisted key/value
110
+ for (const [key, value] of Object.entries(preExisted)) {
111
+ logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
112
+ logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
113
+ }
159
114
 
160
- logger.successv(`injecting env (${result.injected.size}) from encrypted ${envVaultFilepath}`)
161
- } catch (e) {
162
- logger.error(e)
115
+ logger.successv(`injecting env (${uniqueInjectedKeys.length}) from encrypted ${envVaultFile}`)
116
+ } catch (error) {
117
+ logger.error(error.message)
118
+ if (error.help) {
119
+ logger.help(error.help)
163
120
  }
164
121
  }
165
122
  } else {
166
- const { files, readableFilepaths, uniqueInjectedKeys } = new RunDefault(options.envFile, options.env, options.overload).run()
123
+ const {
124
+ files,
125
+ readableFilepaths,
126
+ uniqueInjectedKeys
127
+ } = new RunDefault(options.envFile, options.env, options.overload).run()
167
128
 
168
129
  for (const file of files) {
169
130
  const filepath = file.filepath
@@ -0,0 +1,10 @@
1
+ const decrypt = require('./decrypt')
2
+ const hash = require('./hash')
3
+
4
+ function changed (ciphertext, raw, dotenvKey) {
5
+ const decrypted = decrypt(ciphertext, dotenvKey)
6
+
7
+ return hash(decrypted) !== hash(raw)
8
+ }
9
+
10
+ module.exports = changed
@@ -0,0 +1,25 @@
1
+ const dotenv = require('dotenv')
2
+
3
+ const parseEncryptionKeyFromDotenvKey = require('./parseEncryptionKeyFromDotenvKey')
4
+
5
+ function decrypt (ciphertext, dotenvKey) {
6
+ const key = parseEncryptionKeyFromDotenvKey(dotenvKey)
7
+
8
+ try {
9
+ return dotenv.decrypt(ciphertext, key)
10
+ } catch (e) {
11
+ const error = new Error('[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.')
12
+ error.code = 'DECRYPTION_FAILED'
13
+ error.help = '[DECRYPTION_FAILED] Run with debug flag [dotenvx run --debug -- yourcommand] or manually run [echo $DOTENV_KEY] to compare it to the one in .env.keys.'
14
+ error.debug = `[DECRYPTION_FAILED] DOTENV_KEY is ${dotenvKey}`
15
+
16
+ switch (e.code) {
17
+ case 'DECRYPTION_FAILED':
18
+ throw error
19
+ default:
20
+ throw e
21
+ }
22
+ }
23
+ }
24
+
25
+ module.exports = decrypt
@@ -1,10 +1,8 @@
1
1
  const path = require('path')
2
- const crypto = require('crypto')
3
- const xxhash = require('xxhashjs')
4
- const dotenv = require('dotenv')
5
2
 
6
- const XXHASH_SEED = 0xABCD
7
- const NONCE_BYTES = 12
3
+ const encrypt = require('./encrypt')
4
+ const changed = require('./changed')
5
+ const guessEnvironment = require('./guessEnvironment')
8
6
 
9
7
  class DotenvVault {
10
8
  constructor (dotenvFiles = {}, dotenvKeys = {}, dotenvVaults = {}) {
@@ -19,14 +17,14 @@ class DotenvVault {
19
17
  const addedDotenvFilenames = new Set()
20
18
 
21
19
  for (const [filepath, raw] of Object.entries(this.dotenvFiles)) {
22
- const environment = this._guessEnvironment(filepath)
20
+ const environment = guessEnvironment(filepath)
23
21
  const vault = `DOTENV_VAULT_${environment.toUpperCase()}`
24
22
 
25
23
  let ciphertext = this.dotenvVaults[vault]
26
24
  const dotenvKey = this.dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
27
25
 
28
- if (!ciphertext || ciphertext.length === 0 || this._changed(dotenvKey, ciphertext, raw)) {
29
- ciphertext = this._encrypt(dotenvKey, raw)
26
+ if (!ciphertext || ciphertext.length === 0 || changed(ciphertext, raw, dotenvKey)) {
27
+ ciphertext = encrypt(raw, dotenvKey)
30
28
  this.dotenvVaults[vault] = ciphertext
31
29
  addedVaults.add(vault) // for info logging to user
32
30
 
@@ -54,98 +52,6 @@ class DotenvVault {
54
52
  addedDotenvFilenames: [...addedDotenvFilenames] // return set as array
55
53
  }
56
54
  }
57
-
58
- _guessEnvironment (filepath) {
59
- const filename = path.basename(filepath)
60
- const parts = filename.split('.')
61
- const possibleEnvironment = parts[2] // ['', 'env', environment', 'previous']
62
-
63
- if (!possibleEnvironment || possibleEnvironment.length === 0) {
64
- return 'development'
65
- }
66
-
67
- return possibleEnvironment
68
- }
69
-
70
- _changed (dotenvKey, ciphertext, raw) {
71
- const decrypted = this._decrypt(dotenvKey, ciphertext)
72
-
73
- return this._hash(decrypted) !== this._hash(raw)
74
- }
75
-
76
- _encrypt (dotenvKey, raw) {
77
- const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey)
78
-
79
- // set up nonce
80
- const nonce = crypto.randomBytes(NONCE_BYTES)
81
-
82
- // set up cipher
83
- const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
84
-
85
- // generate ciphertext
86
- let ciphertext = ''
87
- ciphertext += cipher.update(raw, 'utf8', 'hex')
88
- ciphertext += cipher.final('hex')
89
- ciphertext += cipher.getAuthTag().toString('hex')
90
-
91
- // prepend nonce
92
- ciphertext = nonce.toString('hex') + ciphertext
93
-
94
- // base64 encode output
95
- return Buffer.from(ciphertext, 'hex').toString('base64')
96
- }
97
-
98
- _decrypt (dotenvKey, ciphertext) {
99
- const key = this._parseEncryptionKeyFromDotenvKey(dotenvKey)
100
-
101
- try {
102
- return dotenv.decrypt(ciphertext, key)
103
- } catch (e) {
104
- const decryptionFailedError = new Error('[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.')
105
- decryptionFailedError.code = 'DECRYPTION_FAILED'
106
- decryptionFailedError.help = '[DECRYPTION_FAILED] Run with debug flag [dotenvx run --debug -- yourcommand] or manually run [echo $DOTENV_KEY] to compare it to the one in .env.keys.'
107
- decryptionFailedError.debug = `[DECRYPTION_FAILED] DOTENV_KEY is ${dotenvKey}`
108
-
109
- switch (e.code) {
110
- case 'DECRYPTION_FAILED':
111
- throw decryptionFailedError
112
- default:
113
- throw e
114
- }
115
- }
116
- }
117
-
118
- _parseEncryptionKeyFromDotenvKey (dotenvKey) {
119
- // Parse DOTENV_KEY. Format is a URI
120
- let uri
121
- try {
122
- uri = new URL(dotenvKey)
123
- } catch (e) {
124
- const code = 'INVALID_DOTENV_KEY'
125
- const message = `INVALID_DOTENV_KEY: ${e.message}`
126
- const error = new Error(message)
127
- error.code = code
128
-
129
- throw error
130
- }
131
-
132
- // Get decrypt key
133
- const key = uri.password
134
- if (!key) {
135
- const code = 'INVALID_DOTENV_KEY'
136
- const message = 'INVALID_DOTENV_KEY: Missing key part'
137
- const error = new Error(message)
138
- error.code = code
139
-
140
- throw error
141
- }
142
-
143
- return Buffer.from(key.slice(-64), 'hex')
144
- }
145
-
146
- _hash (str) {
147
- return xxhash.h32(str, XXHASH_SEED).toString(16)
148
- }
149
55
  }
150
56
 
151
57
  module.exports = DotenvVault
@@ -0,0 +1,29 @@
1
+ const crypto = require('crypto')
2
+
3
+ const parseEncryptionKeyFromDotenvKey = require('./parseEncryptionKeyFromDotenvKey')
4
+
5
+ const NONCE_BYTES = 12
6
+
7
+ function encrypt (raw, dotenvKey) {
8
+ const key = parseEncryptionKeyFromDotenvKey(dotenvKey)
9
+
10
+ // set up nonce
11
+ const nonce = crypto.randomBytes(NONCE_BYTES)
12
+
13
+ // set up cipher
14
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
15
+
16
+ // generate ciphertext
17
+ let ciphertext = ''
18
+ ciphertext += cipher.update(raw, 'utf8', 'hex')
19
+ ciphertext += cipher.final('hex')
20
+ ciphertext += cipher.getAuthTag().toString('hex')
21
+
22
+ // prepend nonce
23
+ ciphertext = nonce.toString('hex') + ciphertext
24
+
25
+ // base64 encode output
26
+ return Buffer.from(ciphertext, 'hex').toString('base64')
27
+ }
28
+
29
+ module.exports = encrypt
@@ -0,0 +1,15 @@
1
+ const path = require('path')
2
+
3
+ function guessEnvironment (filepath) {
4
+ const filename = path.basename(filepath)
5
+ const parts = filename.split('.')
6
+ const possibleEnvironment = parts[2] // ['', 'env', environment', 'previous']
7
+
8
+ if (!possibleEnvironment || possibleEnvironment.length === 0) {
9
+ return 'development'
10
+ }
11
+
12
+ return possibleEnvironment
13
+ }
14
+
15
+ module.exports = guessEnvironment
@@ -0,0 +1,9 @@
1
+ const xxhash = require('xxhashjs')
2
+
3
+ const XXHASH_SEED = 0xABCD // DO NOT CHANGE
4
+
5
+ function hash (str) {
6
+ return xxhash.h32(str, XXHASH_SEED).toString(16)
7
+ }
8
+
9
+ module.exports = hash
@@ -0,0 +1,27 @@
1
+ function inject (processEnv = {}, parsed = {}, overload = false) {
2
+ const injected = {}
3
+ const preExisted = {}
4
+
5
+ // set processEnv
6
+ for (const key of Object.keys(parsed)) {
7
+ if (processEnv[key]) {
8
+ if (overload === true) {
9
+ processEnv[key] = parsed[key]
10
+
11
+ injected[key] = parsed[key] // track injected key/value
12
+ } else {
13
+ preExisted[key] = processEnv[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
@@ -0,0 +1,19 @@
1
+ function parseEnvironmentFromDotenvKey (dotenvKey) {
2
+ // Parse DOTENV_KEY. Format is a URI
3
+ let uri
4
+ try {
5
+ uri = new URL(dotenvKey)
6
+ } catch (e) {
7
+ throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
8
+ }
9
+
10
+ // Get environment
11
+ const environment = uri.searchParams.get('environment')
12
+ if (!environment) {
13
+ throw new Error('INVALID_DOTENV_KEY: Missing environment part')
14
+ }
15
+
16
+ return environment
17
+ }
18
+
19
+ module.exports = parseEnvironmentFromDotenvKey
@@ -0,0 +1,30 @@
1
+ const dotenv = require('dotenv')
2
+ const dotenvExpand = require('dotenv-expand')
3
+
4
+ function parseExpand (src, overload) {
5
+ const parsed = dotenv.parse(src)
6
+
7
+ // consider moving this logic straight into dotenv-expand
8
+ let inputParsed = {}
9
+ if (overload) {
10
+ inputParsed = { ...process.env, ...parsed }
11
+ } else {
12
+ inputParsed = { ...parsed, ...process.env }
13
+ }
14
+
15
+ const expandPlease = {
16
+ processEnv: {},
17
+ parsed: inputParsed
18
+ }
19
+ const expanded = dotenvExpand.expand(expandPlease).parsed
20
+
21
+ // but then 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
22
+ const result = {}
23
+ for (const key in parsed) {
24
+ result[key] = expanded[key]
25
+ }
26
+
27
+ return result
28
+ }
29
+
30
+ module.exports = parseExpand
@@ -1,10 +1,11 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
- const dotenv = require('dotenv')
4
- const dotenvExpand = require('dotenv-expand')
5
3
 
6
4
  const ENCODING = 'utf8'
7
5
 
6
+ const inject = require('./../helpers/inject')
7
+ const parseExpand = require('./../helpers/parseExpand')
8
+
8
9
  class RunDefault {
9
10
  constructor (envFile = '.env', env = [], overload = false) {
10
11
  this.envFile = envFile
@@ -24,10 +25,10 @@ class RunDefault {
24
25
  row.string = env
25
26
 
26
27
  try {
27
- const parsed = this._parseExpand(env)
28
+ const parsed = parseExpand(env, this.overload)
28
29
  row.parsed = parsed
29
30
 
30
- const { injected, preExisted } = this._inject(process.env, parsed)
31
+ const { injected, preExisted } = inject(process.env, parsed, this.overload)
31
32
  row.injected = injected
32
33
  row.preExisted = preExisted
33
34
 
@@ -51,10 +52,10 @@ class RunDefault {
51
52
  const src = fs.readFileSync(filepath, { encoding: ENCODING })
52
53
  readableFilepaths.add(envFilepath)
53
54
 
54
- const parsed = this._parseExpand(src)
55
+ const parsed = parseExpand(src, this.overload)
55
56
  row.parsed = parsed
56
57
 
57
- const { injected, preExisted } = this._inject(process.env, parsed)
58
+ const { injected, preExisted } = inject(process.env, parsed, this.overload)
58
59
  row.injected = injected
59
60
  row.preExisted = preExisted
60
61
 
@@ -98,58 +99,6 @@ class RunDefault {
98
99
 
99
100
  return this.env
100
101
  }
101
-
102
- _parseExpand (src) {
103
- const parsed = dotenv.parse(src)
104
-
105
- // consider moving this logic straight into dotenv-expand
106
- let inputParsed = {}
107
- if (this.overload) {
108
- inputParsed = { ...process.env, ...parsed }
109
- } else {
110
- inputParsed = { ...parsed, ...process.env }
111
- }
112
-
113
- const expandPlease = {
114
- processEnv: {},
115
- parsed: inputParsed
116
- }
117
- const expanded = dotenvExpand.expand(expandPlease).parsed
118
-
119
- // but then 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
120
- const result = {}
121
- for (const key in parsed) {
122
- result[key] = expanded[key]
123
- }
124
-
125
- return result
126
- }
127
-
128
- _inject (processEnv = {}, parsed = {}) {
129
- const injected = {}
130
- const preExisted = {}
131
-
132
- // set processEnv
133
- for (const key of Object.keys(parsed)) {
134
- if (processEnv[key]) {
135
- if (this.overload === true) {
136
- processEnv[key] = parsed[key]
137
-
138
- injected[key] = parsed[key] // track injected key/value
139
- } else {
140
- preExisted[key] = processEnv[key] // track preExisted key/value
141
- }
142
- } else {
143
- processEnv[key] = parsed[key]
144
- injected[key] = parsed[key] // track injected key/value
145
- }
146
- }
147
-
148
- return {
149
- injected,
150
- preExisted
151
- }
152
- }
153
102
  }
154
103
 
155
104
  module.exports = RunDefault
@@ -0,0 +1,136 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const dotenv = require('dotenv')
4
+
5
+ const inject = require('./../helpers/inject')
6
+ const decrypt = require('./../helpers/decrypt')
7
+ const parseExpand = require('./../helpers/parseExpand')
8
+ const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
9
+
10
+ const ENCODING = 'utf8'
11
+
12
+ class RunVault {
13
+ constructor (envVaultFile = '.env.vault', env = [], DOTENV_KEY = '', overload = false) {
14
+ this.DOTENV_KEY = DOTENV_KEY
15
+ this.envVaultFile = envVaultFile
16
+ this.env = env
17
+ this.overload = overload
18
+ }
19
+
20
+ run () {
21
+ const filepath = path.resolve(this.envVaultFile)
22
+ if (!fs.existsSync(filepath)) {
23
+ const code = 'MISSING_ENV_VAULT_FILE'
24
+ const message = `you set DOTENV_KEY but your .env.vault file is missing: ${filepath}`
25
+ const error = new Error(message)
26
+ error.code = code
27
+ throw error
28
+ }
29
+
30
+ if (this.DOTENV_KEY.length < 1) {
31
+ const code = 'MISSING_DOTENV_KEY'
32
+ const message = `your DOTENV_KEY appears to be blank: '${this.DOTENV_KEY}'`
33
+ const error = new Error(message)
34
+ error.code = code
35
+ throw error
36
+ }
37
+
38
+ const strings = []
39
+ const uniqueInjectedKeys = new Set()
40
+
41
+ const envs = this._envs()
42
+ for (const env of envs) {
43
+ const row = {}
44
+ row.string = env
45
+
46
+ const parsed = parseExpand(env, this.overload)
47
+ row.parsed = parsed
48
+
49
+ const { injected, preExisted } = inject(process.env, parsed, this.overload)
50
+ row.injected = injected
51
+ row.preExisted = preExisted
52
+
53
+ for (const key of Object.keys(injected)) {
54
+ uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
55
+ }
56
+
57
+ strings.push(row)
58
+ }
59
+
60
+ let decrypted
61
+ const dotenvKeys = this._dotenvKeys()
62
+ const parsedVault = this._parsedVault(filepath)
63
+ for (let i = 0; i < dotenvKeys.length; i++) {
64
+ try {
65
+ const dotenvKey = dotenvKeys[i].trim() // dotenv://key_1234@...?environment=prod
66
+
67
+ decrypted = this._decrypted(dotenvKey, parsedVault)
68
+
69
+ break
70
+ } catch (error) {
71
+ // last key
72
+ if (i + 1 >= dotenvKeys.length) {
73
+ throw error
74
+ }
75
+ // try next key
76
+ }
77
+ }
78
+
79
+ // parse this. it's the equivalent of the .env file
80
+ const parsed = parseExpand(decrypted, this.overload)
81
+ const { injected, preExisted } = inject(process.env, parsed, this.overload)
82
+
83
+ for (const key of Object.keys(injected)) {
84
+ uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
85
+ }
86
+
87
+ return {
88
+ envVaultFile: this.envVaultFile, // filepath
89
+ strings,
90
+ dotenvKeys,
91
+ decrypted,
92
+ parsed,
93
+ injected,
94
+ preExisted,
95
+ uniqueInjectedKeys: [...uniqueInjectedKeys]
96
+ }
97
+ }
98
+
99
+ // handle scenario for comma separated keys - for use with key rotation
100
+ // example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
101
+ _dotenvKeys () {
102
+ return this.DOTENV_KEY.split(',')
103
+ }
104
+
105
+ _decrypted (dotenvKey, parsedVault) {
106
+ const environment = parseEnvironmentFromDotenvKey(dotenvKey)
107
+
108
+ // DOTENV_KEY_PRODUCTION
109
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`
110
+ const ciphertext = parsedVault[environmentKey]
111
+ if (!ciphertext) {
112
+ const error = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: cannot locate environment ${environmentKey} in your .env.vault file`)
113
+ error.code = 'NOT_FOUND_DOTENV_ENVIRONMENT'
114
+
115
+ throw error
116
+ }
117
+
118
+ return decrypt(ciphertext, dotenvKey)
119
+ }
120
+
121
+ // { "DOTENV_VAULT_DEVELOPMENT": "<ciphertext>" }
122
+ _parsedVault (filepath) {
123
+ const src = fs.readFileSync(filepath, { encoding: ENCODING })
124
+ return dotenv.parse(src)
125
+ }
126
+
127
+ _envs () {
128
+ if (!Array.isArray(this.env)) {
129
+ return [this.env]
130
+ }
131
+
132
+ return this.env
133
+ }
134
+ }
135
+
136
+ module.exports = RunVault