@dotenvx/dotenvx 0.3.9 → 0.5.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/README.md +6 -1
- package/package.json +3 -2
- package/src/cli/dotenvx.js +194 -32
- package/src/cli/helpers.js +142 -1
- package/src/lib/main.js +17 -7
- package/src/shared/logger.js +5 -1
package/README.md
CHANGED
|
@@ -157,7 +157,12 @@ $ dotenvx run --env-file=.env.local --env-file=.env -- node index.js
|
|
|
157
157
|
|
|
158
158
|
## Encrypt Your Env Files
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
```
|
|
161
|
+
$ dotenvx encrypt
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> This will encrypt your `.env` file to a `.env.vault` file. Commit your `.env.vault` file safely to code.
|
|
165
|
+
> This will also generate a `.env.keys` file. Do NOT commit this file to code. Keep your `.env.keys` secret. 🤫
|
|
161
166
|
|
|
162
167
|
|
|
163
168
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.5.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"commander": "^11.1.0",
|
|
28
28
|
"dotenv": "^16.3.1",
|
|
29
|
-
"winston": "^3.11.0"
|
|
29
|
+
"winston": "^3.11.0",
|
|
30
|
+
"xxhashjs": "^0.2.2"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"jest": "^29.7.0",
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -23,7 +23,7 @@ program
|
|
|
23
23
|
|
|
24
24
|
if (options.logLevel) {
|
|
25
25
|
logger.level = options.logLevel
|
|
26
|
-
logger.debug(`
|
|
26
|
+
logger.debug(`setting log level to ${options.logLevel}`)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// --quiet overides --log-level. only errors will be shown
|
|
@@ -48,63 +48,225 @@ program
|
|
|
48
48
|
.description(packageJson.description)
|
|
49
49
|
.version(packageJson.version)
|
|
50
50
|
|
|
51
|
+
// dotenvx run -- node index.js
|
|
51
52
|
program.command('run')
|
|
52
53
|
.description('inject env variables into your application process')
|
|
53
|
-
.option('-f, --env-file <paths...>', 'path to your env file', '.env')
|
|
54
|
+
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
|
|
54
55
|
.option('-o, --overload', 'override existing env variables')
|
|
55
56
|
.action(function () {
|
|
56
|
-
// injecting 1 environment variable from ${options.envFile}
|
|
57
57
|
const options = this.opts()
|
|
58
|
-
logger.debug('
|
|
58
|
+
logger.debug('configuring options')
|
|
59
59
|
logger.debug(options)
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
// load from .env.vault file
|
|
62
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
63
|
+
const filepath = helpers.resolvePath('.env.vault')
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(filepath)) {
|
|
66
|
+
logger.error(`you set DOTENV_KEY but your .env.vault file is missing: ${filepath}`)
|
|
67
|
+
} else {
|
|
68
|
+
logger.verbose(`injecting encrypted env from ${filepath}`)
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
logger.debug(`reading encrypted env from ${filepath}`)
|
|
72
|
+
const src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
73
|
+
|
|
74
|
+
logger.debug(`parsing encrypted env from ${filepath}`)
|
|
75
|
+
const parsedVault = main.parse(src)
|
|
76
|
+
|
|
77
|
+
logger.debug(`decrypting encrypted env from ${filepath}`)
|
|
78
|
+
// handle scenario for comma separated keys - for use with key rotation
|
|
79
|
+
// example: DOTENV_KEY="dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenv.org/vault/.env.vault?environment=prod"
|
|
80
|
+
const dotenvKeys = process.env.DOTENV_KEY.split(',')
|
|
81
|
+
const length = dotenvKeys.length
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
let decrypted
|
|
84
|
+
for (let i = 0; i < length; i++) {
|
|
85
|
+
try {
|
|
86
|
+
// Get full dotenvKey
|
|
87
|
+
const dotenvKey = dotenvKeys[i].trim()
|
|
70
88
|
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
const key = helpers._parseEncryptionKeyFromDotenvKey(dotenvKey)
|
|
90
|
+
const ciphertext = helpers._parseCipherTextFromDotenvKeyAndParsedVault(dotenvKey, parsedVault)
|
|
73
91
|
|
|
74
|
-
|
|
92
|
+
// Decrypt
|
|
93
|
+
decrypted = main.decrypt(ciphertext, key)
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
break
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// last key
|
|
98
|
+
if (i + 1 >= length) {
|
|
99
|
+
throw error
|
|
100
|
+
}
|
|
101
|
+
// try next key
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
logger.debug(decrypted)
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
106
|
+
logger.debug(`parsing decrypted env from ${filepath}`)
|
|
107
|
+
const parsed = main.parse(decrypted)
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
logger.debug(`writing decrypted env from ${filepath}`)
|
|
110
|
+
const result = main.write(process.env, parsed, options.overload)
|
|
85
111
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
logger.info(`injecting ${result.written.size} environment ${helpers.pluralize('variable', result.written.size)} from encrypted .env.vault`)
|
|
113
|
+
} catch (e) {
|
|
114
|
+
logger.error(e)
|
|
115
|
+
}
|
|
90
116
|
}
|
|
91
|
-
}
|
|
117
|
+
} else {
|
|
118
|
+
// convert to array if needed
|
|
119
|
+
let optionEnvFile = options.envFile
|
|
120
|
+
if (!Array.isArray(optionEnvFile)) {
|
|
121
|
+
optionEnvFile = [optionEnvFile]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const readableFilepaths = new Set()
|
|
125
|
+
const written = new Set()
|
|
126
|
+
|
|
127
|
+
for (const envFilepath of optionEnvFile) {
|
|
128
|
+
const filepath = helpers.resolvePath(envFilepath)
|
|
129
|
+
|
|
130
|
+
logger.verbose(`injecting env from ${filepath}`)
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
logger.debug(`reading env from ${filepath}`)
|
|
134
|
+
const src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
92
135
|
|
|
93
|
-
|
|
94
|
-
|
|
136
|
+
logger.debug(`parsing env from ${filepath}`)
|
|
137
|
+
const parsed = main.parse(src)
|
|
138
|
+
|
|
139
|
+
logger.debug(`writing env from ${filepath}`)
|
|
140
|
+
const result = main.write(process.env, parsed, options.overload)
|
|
141
|
+
|
|
142
|
+
readableFilepaths.add(envFilepath)
|
|
143
|
+
result.written.forEach(key => written.add(key))
|
|
144
|
+
} catch (e) {
|
|
145
|
+
logger.warn(e)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (readableFilepaths.size > 0) {
|
|
150
|
+
logger.info(`injecting ${written.size} environment ${helpers.pluralize('variable', written.size)} from ${[...readableFilepaths]}`)
|
|
151
|
+
}
|
|
95
152
|
}
|
|
96
153
|
|
|
97
154
|
// Extract command and arguments after '--'
|
|
98
155
|
const commandIndex = process.argv.indexOf('--')
|
|
99
156
|
if (commandIndex === -1 || commandIndex === process.argv.length - 1) {
|
|
100
|
-
logger.error('
|
|
101
|
-
logger.error('Exiting')
|
|
157
|
+
logger.error('at least one argument is required after the run command, received 0.')
|
|
102
158
|
process.exit(1)
|
|
103
159
|
} else {
|
|
104
160
|
const subCommand = process.argv.slice(commandIndex + 1)
|
|
105
161
|
|
|
106
|
-
helpers.executeCommand(subCommand, env)
|
|
162
|
+
helpers.executeCommand(subCommand, process.env)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// dotenvx encrypt
|
|
167
|
+
program.command('encrypt')
|
|
168
|
+
.description('encrypt .env.* to .env.vault')
|
|
169
|
+
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', helpers.findEnvFiles('./'))
|
|
170
|
+
.action(function () {
|
|
171
|
+
const options = this.opts()
|
|
172
|
+
logger.debug('configuring options')
|
|
173
|
+
logger.debug(options)
|
|
174
|
+
|
|
175
|
+
let optionEnvFile = options.envFile
|
|
176
|
+
if (!Array.isArray(optionEnvFile)) {
|
|
177
|
+
optionEnvFile = [optionEnvFile]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
logger.verbose(`generating .env.keys from ${optionEnvFile}`)
|
|
182
|
+
|
|
183
|
+
const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {})
|
|
184
|
+
|
|
185
|
+
for (const envFilepath of optionEnvFile) {
|
|
186
|
+
const filepath = helpers.resolvePath(envFilepath)
|
|
187
|
+
if (!fs.existsSync(filepath)) {
|
|
188
|
+
throw new Error(`file does not exist: ${filepath}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const environment = helpers.guessEnvironment(filepath)
|
|
192
|
+
const key = `DOTENV_KEY_${environment.toUpperCase()}`
|
|
193
|
+
|
|
194
|
+
let value = dotenvKeys[key]
|
|
195
|
+
|
|
196
|
+
// first time seeing new DOTENV_KEY_${environment}
|
|
197
|
+
if (!value || value.length === 0) {
|
|
198
|
+
logger.verbose(`generating ${key}`)
|
|
199
|
+
value = helpers.generateDotenvKey(environment)
|
|
200
|
+
logger.debug(`generating ${key} as ${value}`)
|
|
201
|
+
|
|
202
|
+
dotenvKeys[key] = value
|
|
203
|
+
} else {
|
|
204
|
+
logger.verbose(`existing ${key}`)
|
|
205
|
+
logger.debug(`existing ${key} as ${value}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let keysData = `#/!!!!!!!!!!!!!!!!!!!.env.keys!!!!!!!!!!!!!!!!!!!!!!/
|
|
210
|
+
#/ DOTENV_KEYs. DO NOT commit to source control /
|
|
211
|
+
#/ [how it works](https://dotenv.org/env-keys) /
|
|
212
|
+
#/--------------------------------------------------/\n`
|
|
213
|
+
|
|
214
|
+
for (const key in dotenvKeys) {
|
|
215
|
+
const value = dotenvKeys[key]
|
|
216
|
+
keysData += `${key}="${value}"\n`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fs.writeFileSync('.env.keys', keysData)
|
|
220
|
+
} catch (e) {
|
|
221
|
+
logger.error(e)
|
|
222
|
+
process.exit(1)
|
|
107
223
|
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
logger.verbose(`generating .env.vault from ${optionEnvFile}`)
|
|
227
|
+
|
|
228
|
+
const dotenvKeys = (main.configDotenv({ path: '.env.keys' }).parsed || {})
|
|
229
|
+
const dotenvVaults = (main.configDotenv({ path: '.env.vault' }).parsed || {})
|
|
230
|
+
|
|
231
|
+
for (const envFilepath of optionEnvFile) {
|
|
232
|
+
const filepath = helpers.resolvePath(envFilepath)
|
|
233
|
+
const environment = helpers.guessEnvironment(filepath)
|
|
234
|
+
const vault = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
235
|
+
|
|
236
|
+
let ciphertext = dotenvVaults[vault]
|
|
237
|
+
const dotenvKey = dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
|
|
238
|
+
|
|
239
|
+
if (!ciphertext || ciphertext.length === 0 || helpers.changed(ciphertext, dotenvKey, filepath, ENCODING)) {
|
|
240
|
+
logger.verbose(`encrypting ${vault}`)
|
|
241
|
+
ciphertext = helpers.encryptFile(filepath, dotenvKey, ENCODING)
|
|
242
|
+
logger.verbose(`encrypting ${vault} as ${ciphertext}`)
|
|
243
|
+
|
|
244
|
+
dotenvVaults[vault] = ciphertext
|
|
245
|
+
} else {
|
|
246
|
+
logger.verbose(`existing ${vault}`)
|
|
247
|
+
logger.debug(`existing ${vault} as ${ciphertext}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let vaultData = `#/-------------------.env.vault---------------------/
|
|
252
|
+
#/ cloud-agnostic vaulting standard /
|
|
253
|
+
#/ [how it works](https://dotenv.org/env-vault) /
|
|
254
|
+
#/--------------------------------------------------/\n\n`
|
|
255
|
+
|
|
256
|
+
for (const vault in dotenvVaults) {
|
|
257
|
+
const value = dotenvVaults[vault]
|
|
258
|
+
const environment = vault.replace('DOTENV_VAULT_', '').toLowerCase()
|
|
259
|
+
vaultData += `# ${environment}\n`
|
|
260
|
+
vaultData += `${vault}="${value}"\n\n`
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fs.writeFileSync('.env.vault', vaultData)
|
|
264
|
+
} catch (e) {
|
|
265
|
+
logger.error(e)
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
logger.info(`encrypted ${optionEnvFile} to .env.vault`)
|
|
108
270
|
})
|
|
109
271
|
|
|
110
272
|
program.parse(process.argv)
|
package/src/cli/helpers.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
1
2
|
const path = require('path')
|
|
3
|
+
const crypto = require('crypto')
|
|
2
4
|
const { spawn } = require('child_process')
|
|
5
|
+
const xxhash = require('xxhashjs')
|
|
6
|
+
const XXHASH_SEED = 0xABCD
|
|
7
|
+
const NONCE_BYTES = 12
|
|
8
|
+
|
|
9
|
+
const main = require('./../lib/main')
|
|
10
|
+
|
|
11
|
+
const RESERVED_ENV_FILES = ['.env.vault', '.env.projects', '.env.keys', '.env.me', '.env.x']
|
|
3
12
|
|
|
4
13
|
// resolve path based on current running process location
|
|
5
14
|
const resolvePath = function (filepath) {
|
|
@@ -22,7 +31,139 @@ const executeCommand = function (subCommand, env) {
|
|
|
22
31
|
})
|
|
23
32
|
}
|
|
24
33
|
|
|
34
|
+
const pluralize = function (word, count) {
|
|
35
|
+
// simple pluralization: add 's' at the end
|
|
36
|
+
if (count === 0 || count > 1) {
|
|
37
|
+
return word + 's'
|
|
38
|
+
} else {
|
|
39
|
+
return word
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const findEnvFiles = function (directory) {
|
|
44
|
+
const files = fs.readdirSync(directory)
|
|
45
|
+
|
|
46
|
+
const envFiles = files.filter(file =>
|
|
47
|
+
file.startsWith('.env') &&
|
|
48
|
+
!file.endsWith('.previous') &&
|
|
49
|
+
!RESERVED_ENV_FILES.includes(file)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return envFiles
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const guessEnvironment = function (file) {
|
|
56
|
+
const splitFile = file.split('.')
|
|
57
|
+
const possibleEnvironment = splitFile[2] // ['', 'env', environment']
|
|
58
|
+
|
|
59
|
+
if (!possibleEnvironment || possibleEnvironment.length === 0) {
|
|
60
|
+
return 'development'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return possibleEnvironment
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const generateDotenvKey = function (environment) {
|
|
67
|
+
const rand = crypto.randomBytes(32).toString('hex')
|
|
68
|
+
|
|
69
|
+
return `dotenv://:key_${rand}@dotenvx.com/vault/.env.vault?environment=${environment.toLowerCase()}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const encryptFile = function (filepath, dotenvKey, encoding) {
|
|
73
|
+
const key = _parseEncryptionKeyFromDotenvKey(dotenvKey)
|
|
74
|
+
const message = fs.readFileSync(filepath, encoding)
|
|
75
|
+
|
|
76
|
+
const ciphertext = encrypt(key, message)
|
|
77
|
+
|
|
78
|
+
return ciphertext
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const encrypt = function (key, message) {
|
|
82
|
+
// set up nonce
|
|
83
|
+
const nonce = crypto.randomBytes(NONCE_BYTES)
|
|
84
|
+
|
|
85
|
+
// set up cipher
|
|
86
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce)
|
|
87
|
+
|
|
88
|
+
// generate ciphertext
|
|
89
|
+
let ciphertext = ''
|
|
90
|
+
ciphertext += cipher.update(message, 'utf8', 'hex')
|
|
91
|
+
ciphertext += cipher.final('hex')
|
|
92
|
+
ciphertext += cipher.getAuthTag().toString('hex')
|
|
93
|
+
|
|
94
|
+
// prepend nonce
|
|
95
|
+
ciphertext = nonce.toString('hex') + ciphertext
|
|
96
|
+
|
|
97
|
+
// base64 encode output
|
|
98
|
+
return Buffer.from(ciphertext, 'hex').toString('base64')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const changed = function (ciphertext, dotenvKey, filepath, encoding) {
|
|
102
|
+
const key = _parseEncryptionKeyFromDotenvKey(dotenvKey)
|
|
103
|
+
const decrypted = main.decrypt(ciphertext, key)
|
|
104
|
+
const raw = fs.readFileSync(filepath, encoding)
|
|
105
|
+
|
|
106
|
+
return hash(decrypted) !== hash(raw)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const hash = function (str) {
|
|
110
|
+
return xxhash.h32(str, XXHASH_SEED).toString(16)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _parseEncryptionKeyFromDotenvKey = function (dotenvKey) {
|
|
114
|
+
// Parse DOTENV_KEY. Format is a URI
|
|
115
|
+
let uri
|
|
116
|
+
try {
|
|
117
|
+
uri = new URL(dotenvKey)
|
|
118
|
+
} catch (e) {
|
|
119
|
+
throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get decrypt key
|
|
123
|
+
const key = uri.password
|
|
124
|
+
if (!key) {
|
|
125
|
+
throw new Error('INVALID_DOTENV_KEY: Missing key part')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Buffer.from(key.slice(-64), 'hex')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const _parseCipherTextFromDotenvKeyAndParsedVault = function (dotenvKey, parsedVault) {
|
|
132
|
+
// Parse DOTENV_KEY. Format is a URI
|
|
133
|
+
let uri
|
|
134
|
+
try {
|
|
135
|
+
uri = new URL(dotenvKey)
|
|
136
|
+
} catch (e) {
|
|
137
|
+
throw new Error(`INVALID_DOTENV_KEY: ${e.message}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get environment
|
|
141
|
+
const environment = uri.searchParams.get('environment')
|
|
142
|
+
if (!environment) {
|
|
143
|
+
throw new Error('INVALID_DOTENV_KEY: Missing environment part')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get ciphertext payload
|
|
147
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
148
|
+
const ciphertext = parsedVault[environmentKey] // DOTENV_VAULT_PRODUCTION
|
|
149
|
+
if (!ciphertext) {
|
|
150
|
+
throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: cannot locate environment ${environmentKey} in your .env.vault file`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return ciphertext
|
|
154
|
+
}
|
|
155
|
+
|
|
25
156
|
module.exports = {
|
|
26
157
|
resolvePath,
|
|
27
|
-
executeCommand
|
|
158
|
+
executeCommand,
|
|
159
|
+
pluralize,
|
|
160
|
+
findEnvFiles,
|
|
161
|
+
guessEnvironment,
|
|
162
|
+
generateDotenvKey,
|
|
163
|
+
encryptFile,
|
|
164
|
+
encrypt,
|
|
165
|
+
changed,
|
|
166
|
+
hash,
|
|
167
|
+
_parseEncryptionKeyFromDotenvKey,
|
|
168
|
+
_parseCipherTextFromDotenvKeyAndParsedVault
|
|
28
169
|
}
|
package/src/lib/main.js
CHANGED
|
@@ -5,6 +5,14 @@ const config = function (options) {
|
|
|
5
5
|
return dotenv.config(options)
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const decrypt = function (encrypted, keyStr) {
|
|
9
|
+
return dotenv.decrypt(encrypted, keyStr)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const configDotenv = function (options) {
|
|
13
|
+
return dotenv.configDotenv(options)
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
const parse = function (src) {
|
|
9
17
|
const result = dotenv.parse(src)
|
|
10
18
|
|
|
@@ -13,12 +21,12 @@ const parse = function (src) {
|
|
|
13
21
|
return result
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
const
|
|
24
|
+
const write = function (processEnv = {}, parsed = {}, overload = false) {
|
|
17
25
|
if (typeof parsed !== 'object') {
|
|
18
|
-
throw new Error('OBJECT_REQUIRED: Please check the parsed argument being passed to
|
|
26
|
+
throw new Error('OBJECT_REQUIRED: Please check the parsed argument being passed to write')
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
const
|
|
29
|
+
const written = new Set()
|
|
22
30
|
const preExisting = new Set()
|
|
23
31
|
|
|
24
32
|
// set processEnv
|
|
@@ -26,7 +34,7 @@ const populate = function (processEnv = {}, parsed = {}, overload = false) {
|
|
|
26
34
|
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
27
35
|
if (overload === true) {
|
|
28
36
|
processEnv[key] = parsed[key]
|
|
29
|
-
|
|
37
|
+
written.add(key)
|
|
30
38
|
|
|
31
39
|
logger.verbose(`${key} set`)
|
|
32
40
|
logger.debug(`${key} set to ${parsed[key]}`)
|
|
@@ -38,7 +46,7 @@ const populate = function (processEnv = {}, parsed = {}, overload = false) {
|
|
|
38
46
|
}
|
|
39
47
|
} else {
|
|
40
48
|
processEnv[key] = parsed[key]
|
|
41
|
-
|
|
49
|
+
written.add(key)
|
|
42
50
|
|
|
43
51
|
logger.verbose(`${key} set`)
|
|
44
52
|
logger.debug(`${key} set to ${parsed[key]}`)
|
|
@@ -46,13 +54,15 @@ const populate = function (processEnv = {}, parsed = {}, overload = false) {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
return {
|
|
49
|
-
|
|
57
|
+
written,
|
|
50
58
|
preExisting
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
module.exports = {
|
|
55
63
|
config,
|
|
64
|
+
configDotenv,
|
|
65
|
+
decrypt,
|
|
56
66
|
parse,
|
|
57
|
-
|
|
67
|
+
write
|
|
58
68
|
}
|
package/src/shared/logger.js
CHANGED
|
@@ -7,10 +7,14 @@ const transports = winston.transports
|
|
|
7
7
|
|
|
8
8
|
const packageJson = require('./packageJson')
|
|
9
9
|
|
|
10
|
+
function pad (word) {
|
|
11
|
+
return word.padEnd(9, ' ')
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
|
|
11
15
|
const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
|
|
12
16
|
|
|
13
|
-
return `[dotenvx@${packageJson.version}][${level.toUpperCase()}] ${formattedMessage}`
|
|
17
|
+
return `[dotenvx@${packageJson.version}]${pad(`[${level.toUpperCase()}]`)} ${formattedMessage}`
|
|
14
18
|
})
|
|
15
19
|
|
|
16
20
|
const logger = createLogger({
|