@dotenvx/dotenvx 1.9.0 → 1.10.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 +13 -1
- package/package.json +2 -3
- package/src/cli/commands/ext.js +3 -2
- package/src/cli/examples.js +0 -27
- package/src/lib/helpers/decryptValue.js +30 -24
- package/src/lib/helpers/executeExtension.js +4 -4
- package/src/lib/helpers/parseDecryptEvalExpand.js +21 -25
- package/src/lib/helpers/truncate.js +6 -2
- package/src/lib/main.d.ts +0 -31
- package/src/lib/main.js +1 -44
- package/src/cli/actions/ext/vault/decrypt.js +0 -64
- package/src/cli/actions/ext/vault/encrypt.js +0 -78
- package/src/cli/actions/ext/vault/migrate.js +0 -30
- package/src/cli/actions/ext/vault/status.js +0 -78
- package/src/cli/commands/ext/vault.js +0 -36
- package/src/lib/helpers/changed.js +0 -10
- package/src/lib/helpers/containsDirectory.js +0 -7
- package/src/lib/helpers/dotenvKeys.js +0 -57
- package/src/lib/helpers/dotenvVault.js +0 -60
- package/src/lib/helpers/hash.js +0 -9
- package/src/lib/helpers/removePersonal.js +0 -9
- package/src/lib/services/status.js +0 -114
- package/src/lib/services/vaultDecrypt.js +0 -126
- package/src/lib/services/vaultEncrypt.js +0 -118
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
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.
|
|
5
|
+
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.10.0...main)
|
|
6
|
+
|
|
7
|
+
## 1.10.0
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
* remove `dotenvx ext vault`, replace with [dotenvx-ext-vault](https://github.com/dotenvx/dotenvx-ext-vaut) (install there to continue using `ext vault`) ([#351](https://github.com/dotenvx/dotenvx/pull/351))
|
|
12
|
+
|
|
13
|
+
## 1.9.1
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* warn if private key is missing or blank ([#349](https://github.com/dotenvx/dotenvx/pull/349))
|
|
6
18
|
|
|
7
19
|
## 1.9.0
|
|
8
20
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.10.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -45,8 +45,7 @@
|
|
|
45
45
|
"object-treeify": "1.1.33",
|
|
46
46
|
"picomatch": "^4.0.2",
|
|
47
47
|
"tinyexec": "^0.2.0",
|
|
48
|
-
"which": "^4.0.0"
|
|
49
|
-
"xxhashjs": "^0.2.2"
|
|
48
|
+
"which": "^4.0.0"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"capture-console": "^1.0.2",
|
package/src/cli/commands/ext.js
CHANGED
|
@@ -9,6 +9,9 @@ ext
|
|
|
9
9
|
.description('🔌 extensions')
|
|
10
10
|
.allowUnknownOption()
|
|
11
11
|
|
|
12
|
+
// list known extensions here you want to display
|
|
13
|
+
ext.addHelpText('after', ' vault 🔐 manage .env.vault files')
|
|
14
|
+
|
|
12
15
|
ext
|
|
13
16
|
.argument('[command]', 'dynamic ext command')
|
|
14
17
|
.argument('[args...]', 'dynamic ext command arguments')
|
|
@@ -55,6 +58,4 @@ ext.command('scan')
|
|
|
55
58
|
.description('scan for leaked secrets')
|
|
56
59
|
.action(require('./../actions/ext/scan'))
|
|
57
60
|
|
|
58
|
-
ext.addCommand(require('./../commands/ext/vault'))
|
|
59
|
-
|
|
60
61
|
module.exports = ext
|
package/src/cli/examples.js
CHANGED
|
@@ -22,32 +22,6 @@ Try it:
|
|
|
22
22
|
`
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const vaultEncrypt = function () {
|
|
26
|
-
return `
|
|
27
|
-
Examples:
|
|
28
|
-
|
|
29
|
-
\`\`\`
|
|
30
|
-
$ dotenvx ext vault encrypt
|
|
31
|
-
\`\`\`
|
|
32
|
-
|
|
33
|
-
Try it:
|
|
34
|
-
|
|
35
|
-
\`\`\`
|
|
36
|
-
$ echo "HELLO=World" > .env
|
|
37
|
-
$ echo "HELLO=production" > .env.production
|
|
38
|
-
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
39
|
-
|
|
40
|
-
$ dotenvx ext vault encrypt
|
|
41
|
-
encrypted to .env.vault (.env,.env.production)
|
|
42
|
-
keys added to .env.keys (DOTENV_KEY_PRODUCTION,DOTENV_KEY_PRODUCTION)
|
|
43
|
-
|
|
44
|
-
$ DOTENV_KEY='<dotenv_key_production>' dotenvx run -- node index.js
|
|
45
|
-
[dotenvx] injecting env (1) from encrypted .env.vault
|
|
46
|
-
Hello production
|
|
47
|
-
\`\`\`
|
|
48
|
-
`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
25
|
const precommit = function () {
|
|
52
26
|
return `
|
|
53
27
|
Examples:
|
|
@@ -117,7 +91,6 @@ Examples:
|
|
|
117
91
|
|
|
118
92
|
module.exports = {
|
|
119
93
|
run,
|
|
120
|
-
vaultEncrypt,
|
|
121
94
|
precommit,
|
|
122
95
|
prebuild,
|
|
123
96
|
gitignore,
|
|
@@ -3,35 +3,41 @@ const { decrypt } = require('eciesjs')
|
|
|
3
3
|
const PREFIX = 'encrypted:'
|
|
4
4
|
|
|
5
5
|
function decryptValue (value, privateKey) {
|
|
6
|
+
let decryptedValue
|
|
7
|
+
let decryptionError
|
|
8
|
+
|
|
6
9
|
if (!value.startsWith(PREFIX)) {
|
|
7
10
|
return value
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
13
|
+
privateKey = privateKey || ''
|
|
14
|
+
if (privateKey.length <= 0) {
|
|
15
|
+
decryptionError = new Error('private key missing or blank')
|
|
16
|
+
decryptionError.code = 'DECRYPTION_FAILED'
|
|
17
|
+
} else {
|
|
18
|
+
const privateKeys = privateKey.split(',')
|
|
19
|
+
for (const key of privateKeys) {
|
|
20
|
+
const secret = Buffer.from(key, 'hex')
|
|
21
|
+
const encoded = value.substring(PREFIX.length)
|
|
22
|
+
const ciphertext = Buffer.from(encoded, 'base64')
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
decryptedValue = decrypt(secret, ciphertext).toString()
|
|
26
|
+
decryptionError = null // reset to null error (scenario for multiple private keys)
|
|
27
|
+
break
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e.message === 'Invalid private key') {
|
|
30
|
+
decryptionError = new Error('private key looks invalid')
|
|
31
|
+
} else if (e.message === 'Unsupported state or unable to authenticate data') {
|
|
32
|
+
decryptionError = new Error('private key looks wrong')
|
|
33
|
+
} else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {
|
|
34
|
+
decryptionError = new Error('encrypted data looks malformed')
|
|
35
|
+
} else {
|
|
36
|
+
decryptionError = new Error(`${e.message}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
decryptionError.code = 'DECRYPTION_FAILED'
|
|
32
40
|
}
|
|
33
|
-
|
|
34
|
-
decryptionError.code = 'DECRYPTION_FAILED'
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -22,10 +22,10 @@ function executeExtension (ext, command, rawArgs) {
|
|
|
22
22
|
|
|
23
23
|
const result = childProcess.spawnSync(`dotenvx-ext-${command}`, forwardedArgs, { stdio: 'inherit', env })
|
|
24
24
|
if (result.error) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
// list known extension here for convenience to the user
|
|
26
|
+
if (['vault', 'hub'].includes(command)) {
|
|
27
|
+
logger.warn(`[INSTALLATION_NEEDED] install dotenvx-ext-${command} to use [dotenvx ext ${command}] commands`)
|
|
28
|
+
logger.help('? see installation instructions [https://github.com/dotenvx/dotenvx-ext-vault]')
|
|
29
29
|
} else {
|
|
30
30
|
logger.info(`error: unknown command '${command}'`)
|
|
31
31
|
}
|
|
@@ -4,8 +4,8 @@ const dotenvExpand = require('./dotenvExpand')
|
|
|
4
4
|
const decryptValue = require('./decryptValue')
|
|
5
5
|
const truncate = require('./truncate')
|
|
6
6
|
|
|
7
|
-
function warning (e, key, privateKey) {
|
|
8
|
-
const warning = new Error(`[${e.code}] could not decrypt ${key} using private key ${truncate(privateKey)}`)
|
|
7
|
+
function warning (e, key, privateKey = null) {
|
|
8
|
+
const warning = new Error(`[${e.code}] could not decrypt ${key} using private key '${truncate(privateKey)}'`)
|
|
9
9
|
warning.code = e.code
|
|
10
10
|
warning.help = `[${e.code}] ? ${e.message}`
|
|
11
11
|
|
|
@@ -17,14 +17,12 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
|
|
|
17
17
|
|
|
18
18
|
// parse
|
|
19
19
|
const parsed = dotenv.parse(src)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// do nothing. warnings tracked further below.
|
|
27
|
-
}
|
|
20
|
+
for (const key in parsed) {
|
|
21
|
+
try {
|
|
22
|
+
const decryptedValue = decryptValue(parsed[key], privateKey)
|
|
23
|
+
parsed[key] = decryptedValue
|
|
24
|
+
} catch (_e) {
|
|
25
|
+
// do nothing. warnings tracked further below.
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -41,22 +39,20 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
|
|
|
41
39
|
parsed: evaled
|
|
42
40
|
}
|
|
43
41
|
const expanded = dotenvExpand.expand(inputEvaled)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
warnings.push(warning(e, key, privateKey))
|
|
51
|
-
}
|
|
42
|
+
for (const key in expanded.parsed) {
|
|
43
|
+
try {
|
|
44
|
+
const decryptedValue = decryptValue(expanded.parsed[key], privateKey)
|
|
45
|
+
expanded.parsed[key] = decryptedValue
|
|
46
|
+
} catch (e) {
|
|
47
|
+
warnings.push(warning(e, key, privateKey))
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
}
|
|
50
|
+
for (const key in processEnv) {
|
|
51
|
+
try {
|
|
52
|
+
const decryptedValue = decryptValue(processEnv[key], privateKey)
|
|
53
|
+
processEnv[key] = decryptedValue
|
|
54
|
+
} catch (e) {
|
|
55
|
+
warnings.push(warning(e, key, privateKey))
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
58
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
function truncate (str, showChar = 7) {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
if (str && str.length > 0) {
|
|
3
|
+
const visiblePart = str.slice(0, showChar)
|
|
4
|
+
return visiblePart + '…'
|
|
5
|
+
} else {
|
|
6
|
+
return ''
|
|
7
|
+
}
|
|
4
8
|
}
|
|
5
9
|
|
|
6
10
|
module.exports = truncate
|
package/src/lib/main.d.ts
CHANGED
|
@@ -180,18 +180,6 @@ export function encrypt(
|
|
|
180
180
|
key?: string | string[]
|
|
181
181
|
): EncryptOutput;
|
|
182
182
|
|
|
183
|
-
export type VaultEncryptOutput = {
|
|
184
|
-
dotenvKeys: Record<string, string>;
|
|
185
|
-
dotenvKeysFile: string;
|
|
186
|
-
addedKeys: string[];
|
|
187
|
-
existingKeys: string[];
|
|
188
|
-
dotenvVaultFile: string;
|
|
189
|
-
addedVaults: string[];
|
|
190
|
-
existingVaults: string[];
|
|
191
|
-
addedDotenvFilenames: string[];
|
|
192
|
-
envFile: string | string[];
|
|
193
|
-
};
|
|
194
|
-
|
|
195
183
|
/**
|
|
196
184
|
* List all env files in the current working directory
|
|
197
185
|
*
|
|
@@ -288,22 +276,3 @@ export function genexample(
|
|
|
288
276
|
directory: string,
|
|
289
277
|
envFile: string
|
|
290
278
|
): GenExampleOutput;
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Decrypt ciphertext
|
|
294
|
-
* @param encrypted - the encrypted ciphertext string
|
|
295
|
-
* @param keyStr - the decryption key string
|
|
296
|
-
*/
|
|
297
|
-
export function vaultDecrypt(encrypted: string, keyStr: string): string;
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Encrypt plaintext
|
|
301
|
-
*
|
|
302
|
-
* @see https://dotenvx.com/docs
|
|
303
|
-
* @param directory - current working directory
|
|
304
|
-
* @param envFile - path to the .env file(s)
|
|
305
|
-
*/
|
|
306
|
-
export function vaultEncrypt(
|
|
307
|
-
directory?: string,
|
|
308
|
-
envFile?: string | string[]
|
|
309
|
-
): VaultEncryptOutput;
|
package/src/lib/main.js
CHANGED
|
@@ -8,11 +8,9 @@ const Ls = require('./services/ls')
|
|
|
8
8
|
const Get = require('./services/get')
|
|
9
9
|
const Run = require('./services/run')
|
|
10
10
|
const Sets = require('./services/sets')
|
|
11
|
-
const Status = require('./services/status')
|
|
12
11
|
const Encrypt = require('./services/encrypt')
|
|
13
12
|
const Decrypt = require('./services/decrypt')
|
|
14
13
|
const Genexample = require('./services/genexample')
|
|
15
|
-
const VaultEncrypt = require('./services/vaultEncrypt')
|
|
16
14
|
|
|
17
15
|
// helpers
|
|
18
16
|
const dotenvOptionPaths = require('./helpers/dotenvOptionPaths')
|
|
@@ -198,43 +196,6 @@ const decrypt = function (envFile, key, excludeKey) {
|
|
|
198
196
|
return new Decrypt(envFile, key, excludeKey).run()
|
|
199
197
|
}
|
|
200
198
|
|
|
201
|
-
/** @type {import('./main').status} */
|
|
202
|
-
const status = function (directory) {
|
|
203
|
-
return new Status(directory).run()
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// misc/cleanup
|
|
207
|
-
|
|
208
|
-
/** @type {import('./main').vaultDecrypt} */
|
|
209
|
-
const vaultDecrypt = function (encrypted, keyStr) {
|
|
210
|
-
try {
|
|
211
|
-
return dotenv.decrypt(encrypted, keyStr)
|
|
212
|
-
} catch (e) {
|
|
213
|
-
switch (e.code) {
|
|
214
|
-
case 'DECRYPTION_FAILED':
|
|
215
|
-
// more helpful error when decryption fails
|
|
216
|
-
logger.error(
|
|
217
|
-
'[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.'
|
|
218
|
-
)
|
|
219
|
-
logger.help(
|
|
220
|
-
'[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.'
|
|
221
|
-
)
|
|
222
|
-
logger.debug(
|
|
223
|
-
`[DECRYPTION_FAILED] DOTENV_KEY is ${process.env.DOTENV_KEY}`
|
|
224
|
-
)
|
|
225
|
-
process.exit(1)
|
|
226
|
-
break
|
|
227
|
-
default:
|
|
228
|
-
throw e
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** @type {import('./main').vaultEncrypt} */
|
|
234
|
-
const vaultEncrypt = function (directory, envFile) {
|
|
235
|
-
return new VaultEncrypt(directory, envFile).run()
|
|
236
|
-
}
|
|
237
|
-
|
|
238
199
|
module.exports = {
|
|
239
200
|
// dotenv proxies
|
|
240
201
|
config,
|
|
@@ -246,9 +207,5 @@ module.exports = {
|
|
|
246
207
|
ls,
|
|
247
208
|
get,
|
|
248
209
|
set,
|
|
249
|
-
|
|
250
|
-
genexample,
|
|
251
|
-
// misc/cleanup
|
|
252
|
-
vaultEncrypt,
|
|
253
|
-
vaultDecrypt
|
|
210
|
+
genexample
|
|
254
211
|
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
|
|
3
|
-
const { logger } = require('./../../../../shared/logger')
|
|
4
|
-
|
|
5
|
-
const VaultDecrypt = require('./../../../../lib/services/vaultDecrypt')
|
|
6
|
-
|
|
7
|
-
function decrypt (directory) {
|
|
8
|
-
logger.debug(`directory: ${directory}`)
|
|
9
|
-
|
|
10
|
-
const options = this.opts()
|
|
11
|
-
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const {
|
|
15
|
-
processedEnvs,
|
|
16
|
-
changedFilenames,
|
|
17
|
-
unchangedFilenames
|
|
18
|
-
} = new VaultDecrypt(directory, options.environment).run()
|
|
19
|
-
|
|
20
|
-
for (const env of processedEnvs) {
|
|
21
|
-
if (env.warning) {
|
|
22
|
-
const warning = env.warning
|
|
23
|
-
logger.warn(warning.message)
|
|
24
|
-
} else {
|
|
25
|
-
if (env.shouldWrite) {
|
|
26
|
-
logger.debug(`writing ${env.filepath}`)
|
|
27
|
-
fs.writeFileSync(env.filepath, env.decrypted)
|
|
28
|
-
} else {
|
|
29
|
-
logger.debug(`no changes for ${env.filename}`)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let changedMsg = ''
|
|
35
|
-
if (changedFilenames.length > 0) {
|
|
36
|
-
changedMsg = `decrypted (${changedFilenames.join(', ')})`
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let unchangedMsg = ''
|
|
40
|
-
if (unchangedFilenames.length > 0) {
|
|
41
|
-
unchangedMsg = `no changes (${unchangedFilenames.join(', ')})`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (changedMsg.length > 0) {
|
|
45
|
-
logger.success(`${changedMsg} ${unchangedMsg}`)
|
|
46
|
-
} else {
|
|
47
|
-
logger.blank(`${unchangedMsg}`)
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(error.message)
|
|
51
|
-
if (error.help) {
|
|
52
|
-
logger.help(error.help)
|
|
53
|
-
}
|
|
54
|
-
if (error.debug) {
|
|
55
|
-
logger.debug(error.debug)
|
|
56
|
-
}
|
|
57
|
-
if (error.code) {
|
|
58
|
-
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
59
|
-
}
|
|
60
|
-
process.exit(1)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
module.exports = decrypt
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
|
|
4
|
-
const main = require('./../../../../lib/main')
|
|
5
|
-
const { logger } = require('./../../../../shared/logger')
|
|
6
|
-
const pluralize = require('./../../../../lib/helpers/pluralize')
|
|
7
|
-
|
|
8
|
-
function encrypt (directory) {
|
|
9
|
-
logger.debug(`directory: ${directory}`)
|
|
10
|
-
|
|
11
|
-
const options = this.opts()
|
|
12
|
-
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const {
|
|
16
|
-
dotenvKeys,
|
|
17
|
-
dotenvKeysFile,
|
|
18
|
-
addedKeys,
|
|
19
|
-
existingKeys,
|
|
20
|
-
dotenvVaultFile,
|
|
21
|
-
addedVaults,
|
|
22
|
-
existingVaults,
|
|
23
|
-
addedDotenvFilenames,
|
|
24
|
-
envFile
|
|
25
|
-
} = main.vaultEncrypt(directory, options.envFile)
|
|
26
|
-
|
|
27
|
-
logger.verbose(`generating .env.keys from ${envFile}`)
|
|
28
|
-
if (addedKeys.length > 0) {
|
|
29
|
-
logger.verbose(`generated ${addedKeys}`)
|
|
30
|
-
}
|
|
31
|
-
if (existingKeys.length > 0) {
|
|
32
|
-
logger.verbose(`existing ${existingKeys}`)
|
|
33
|
-
}
|
|
34
|
-
fs.writeFileSync(path.resolve(directory, '.env.keys'), dotenvKeysFile)
|
|
35
|
-
|
|
36
|
-
logger.verbose(`generating .env.vault from ${envFile}`)
|
|
37
|
-
if (addedVaults.length > 0) {
|
|
38
|
-
logger.verbose(`encrypting ${addedVaults}`)
|
|
39
|
-
}
|
|
40
|
-
if (existingVaults.length > 0) {
|
|
41
|
-
logger.verbose(`existing ${existingVaults}`)
|
|
42
|
-
}
|
|
43
|
-
fs.writeFileSync(path.resolve(directory, '.env.vault'), dotenvVaultFile)
|
|
44
|
-
|
|
45
|
-
if (addedDotenvFilenames.length > 0) {
|
|
46
|
-
logger.success(`encrypted to .env.vault (${addedDotenvFilenames})`)
|
|
47
|
-
logger.help2('ℹ commit .env.vault to code: [git commit -am ".env.vault"]')
|
|
48
|
-
} else {
|
|
49
|
-
logger.blank(`no changes (${envFile})`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (addedKeys.length > 0) {
|
|
53
|
-
logger.success(`${pluralize('key', addedKeys.length)} added to .env.keys (${addedKeys})`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (addedVaults.length > 0) {
|
|
57
|
-
const DOTENV_VAULT_X = addedVaults[addedVaults.length - 1]
|
|
58
|
-
const DOTENV_KEY_X = DOTENV_VAULT_X.replace('_VAULT_', '_KEY_')
|
|
59
|
-
const tryKey = dotenvKeys[DOTENV_KEY_X]
|
|
60
|
-
|
|
61
|
-
logger.help2(`ℹ run [DOTENV_KEY='${tryKey}' dotenvx run -- yourcommand] to test decryption locally`)
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
logger.error(error.message)
|
|
65
|
-
if (error.help) {
|
|
66
|
-
logger.help(error.help)
|
|
67
|
-
}
|
|
68
|
-
if (error.debug) {
|
|
69
|
-
logger.debug(error.debug)
|
|
70
|
-
}
|
|
71
|
-
if (error.code) {
|
|
72
|
-
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
73
|
-
}
|
|
74
|
-
process.exit(1)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = encrypt
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const { logger } = require('./../../../../shared/logger')
|
|
2
|
-
|
|
3
|
-
function migrate () {
|
|
4
|
-
const options = this.opts()
|
|
5
|
-
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
6
|
-
|
|
7
|
-
logger.help2('To migrate your .env.vault file to encrypted .env file(s):')
|
|
8
|
-
logger.help('')
|
|
9
|
-
logger.help(' 1. Run [dotenvx ext vault decrypt]')
|
|
10
|
-
logger.help(' 2. Run [ls -a .env*]')
|
|
11
|
-
logger.help('')
|
|
12
|
-
logger.help2('Lastly, encrypt each .env(.environment) file:')
|
|
13
|
-
logger.help('')
|
|
14
|
-
logger.help(' 3. Run [dotenvx encrypt -f .env.production]')
|
|
15
|
-
logger.help2('')
|
|
16
|
-
logger.help2('For example:')
|
|
17
|
-
logger.help2('')
|
|
18
|
-
logger.help2(' $ dotenvx encrypt -f .env')
|
|
19
|
-
logger.help2(' $ dotenvx encrypt -f .env.ci')
|
|
20
|
-
logger.help2(' $ dotenvx encrypt -f .env.production')
|
|
21
|
-
logger.help2('')
|
|
22
|
-
logger.help2('Afterward:')
|
|
23
|
-
logger.help2('')
|
|
24
|
-
logger.help2('Update production with your new DOTENV_PRIVATE_KEY_PRODUCTION located in .env.keys')
|
|
25
|
-
logger.help2('')
|
|
26
|
-
logger.success('Learn more at [https://dotenvx.com/docs/quickstart#add-encryption]')
|
|
27
|
-
logger.help2('')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = migrate
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const { logger } = require('./../../../../shared/logger')
|
|
2
|
-
|
|
3
|
-
const main = require('./../../../../lib/main')
|
|
4
|
-
|
|
5
|
-
function status (directory) {
|
|
6
|
-
// debug args
|
|
7
|
-
logger.debug(`directory: ${directory}`)
|
|
8
|
-
|
|
9
|
-
const options = this.opts()
|
|
10
|
-
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const { changes, nochanges, untracked } = main.status(directory)
|
|
14
|
-
|
|
15
|
-
const changeFilenames = []
|
|
16
|
-
const nochangeFilenames = []
|
|
17
|
-
const untrackedFilenames = []
|
|
18
|
-
|
|
19
|
-
for (const row of nochanges) {
|
|
20
|
-
nochangeFilenames.push(row.filename)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (nochangeFilenames.length > 0) {
|
|
24
|
-
logger.blank(`no changes (${nochangeFilenames.join(', ')})`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
for (const row of changes) {
|
|
28
|
-
changeFilenames.push(row.filename)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (changeFilenames.length > 0) {
|
|
32
|
-
logger.warn(`changes (${changeFilenames.join(', ')})`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for (const row of changes) {
|
|
36
|
-
logger.blank('')
|
|
37
|
-
const padding = ' '
|
|
38
|
-
logger.blank(`${padding}\`\`\`${row.filename}`)
|
|
39
|
-
const paddedResult = row.coloredDiff.split('\n').map(line => padding + line).join('\n')
|
|
40
|
-
console.log(paddedResult)
|
|
41
|
-
logger.blank(`${padding}\`\`\``)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (changeFilenames.length > 0) {
|
|
45
|
-
logger.blank('')
|
|
46
|
-
const optionalDirectory = directory === '.' ? '' : ` ${directory}`
|
|
47
|
-
logger.blank(`run [dotenvx encrypt${optionalDirectory}] to apply changes to .env.vault`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (nochangeFilenames.length < 1 && changeFilenames.length < 1) {
|
|
51
|
-
logger.warn('no .env* files.')
|
|
52
|
-
logger.help('? add one with [echo "HELLO=World" > .env] and then run [dotenvx status]')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const row of untracked) {
|
|
56
|
-
untrackedFilenames.push(row.filename)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (untrackedFilenames.length > 0) {
|
|
60
|
-
logger.warn(`untracked (${untrackedFilenames.join(', ')})`)
|
|
61
|
-
logger.help(`? track them with [dotenvx ext vault encrypt ${directory}]`)
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
logger.error(error.message)
|
|
65
|
-
if (error.help) {
|
|
66
|
-
logger.help(error.help)
|
|
67
|
-
}
|
|
68
|
-
if (error.debug) {
|
|
69
|
-
logger.debug(error.debug)
|
|
70
|
-
}
|
|
71
|
-
if (error.code) {
|
|
72
|
-
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
73
|
-
}
|
|
74
|
-
process.exit(1)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = status
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const { Command } = require('commander')
|
|
2
|
-
|
|
3
|
-
const examples = require('./../../examples')
|
|
4
|
-
|
|
5
|
-
const vault = new Command('vault')
|
|
6
|
-
|
|
7
|
-
vault
|
|
8
|
-
.description('🔐 manage .env.vault files')
|
|
9
|
-
|
|
10
|
-
// dotenvx ext vault migrate
|
|
11
|
-
vault.command('migrate')
|
|
12
|
-
.description('instructions for migrating .env.vault to encrypted env file(s)')
|
|
13
|
-
.action(require('./../../actions/ext/vault/migrate'))
|
|
14
|
-
|
|
15
|
-
// dotenvx ext vault encrypt
|
|
16
|
-
vault.command('encrypt')
|
|
17
|
-
.description('encrypt .env.* to .env.vault')
|
|
18
|
-
.addHelpText('after', examples.vaultEncrypt)
|
|
19
|
-
.argument('[directory]', 'directory to encrypt', '.')
|
|
20
|
-
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
21
|
-
.action(require('./../../actions/ext/vault/encrypt'))
|
|
22
|
-
|
|
23
|
-
// dotenvx ext vault decrypt
|
|
24
|
-
vault.command('decrypt')
|
|
25
|
-
.description('decrypt .env.vault to .env*')
|
|
26
|
-
.argument('[directory]', 'directory to decrypt', '.')
|
|
27
|
-
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
28
|
-
.action(require('./../../actions/ext/vault/decrypt'))
|
|
29
|
-
|
|
30
|
-
// dotenvx ext vault status
|
|
31
|
-
vault.command('status')
|
|
32
|
-
.description('compare your .env* content(s) to your .env.vault decrypted content(s)')
|
|
33
|
-
.argument('[directory]', 'directory to check status against', '.')
|
|
34
|
-
.action(require('./../../actions/ext/vault/status'))
|
|
35
|
-
|
|
36
|
-
module.exports = vault
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const crypto = require('crypto')
|
|
2
|
-
|
|
3
|
-
const guessEnvironment = require('./guessEnvironment')
|
|
4
|
-
|
|
5
|
-
class DotenvKeys {
|
|
6
|
-
constructor (envFilepaths = [], dotenvKeys = {}) {
|
|
7
|
-
this.envFilepaths = envFilepaths // pass .env* filepaths to be encrypted
|
|
8
|
-
this.dotenvKeys = dotenvKeys // pass current parsed dotenv keys from .env.keys file
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
run () {
|
|
12
|
-
const addedKeys = new Set()
|
|
13
|
-
const existingKeys = new Set()
|
|
14
|
-
|
|
15
|
-
for (const filepath of this.envFilepaths) {
|
|
16
|
-
const environment = guessEnvironment(filepath)
|
|
17
|
-
const key = `DOTENV_KEY_${environment.toUpperCase()}`
|
|
18
|
-
|
|
19
|
-
let value = this.dotenvKeys[key]
|
|
20
|
-
|
|
21
|
-
// first time seeing new DOTENV_KEY_${environment}
|
|
22
|
-
if (!value || value.length === 0) {
|
|
23
|
-
value = this._generateDotenvKey(environment)
|
|
24
|
-
|
|
25
|
-
this.dotenvKeys[key] = value
|
|
26
|
-
|
|
27
|
-
addedKeys.add(key) // for info logging to user
|
|
28
|
-
} else {
|
|
29
|
-
existingKeys.add(key) // for info logging to user
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let keysData = `#/!!!!!!!!!!!!!!!!!!!.env.keys!!!!!!!!!!!!!!!!!!!!!!/
|
|
34
|
-
#/ DOTENV_KEYs. DO NOT commit to source control /
|
|
35
|
-
#/ [how it works](https://dotenvx.com/env-keys) /
|
|
36
|
-
#/--------------------------------------------------/\n`
|
|
37
|
-
|
|
38
|
-
for (const [key, value] of Object.entries(this.dotenvKeys)) {
|
|
39
|
-
keysData += `${key}="${value}"\n`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
dotenvKeys: this.dotenvKeys,
|
|
44
|
-
dotenvKeysFile: keysData,
|
|
45
|
-
addedKeys: [...addedKeys], // return set as array
|
|
46
|
-
existingKeys: [...existingKeys] // return set as array
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
_generateDotenvKey (environment) {
|
|
51
|
-
const rand = crypto.randomBytes(32).toString('hex')
|
|
52
|
-
|
|
53
|
-
return `dotenv://:key_${rand}@dotenvx.com/vault/.env.vault?environment=${environment.toLowerCase()}`
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = DotenvKeys
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
|
|
3
|
-
const encrypt = require('./encrypt')
|
|
4
|
-
const changed = require('./changed')
|
|
5
|
-
const guessEnvironment = require('./guessEnvironment')
|
|
6
|
-
const removePersonal = require('./removePersonal')
|
|
7
|
-
|
|
8
|
-
class DotenvVault {
|
|
9
|
-
constructor (dotenvFiles = {}, dotenvKeys = {}, dotenvVaults = {}) {
|
|
10
|
-
this.dotenvFiles = dotenvFiles // key: filepath and value: filecontent
|
|
11
|
-
this.dotenvKeys = dotenvKeys // pass current parsed dotenv keys from .env.keys
|
|
12
|
-
this.dotenvVaults = dotenvVaults // pass current parsed dotenv vaults from .env.vault
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
run () {
|
|
16
|
-
const addedVaults = new Set()
|
|
17
|
-
const existingVaults = new Set()
|
|
18
|
-
const addedDotenvFilenames = new Set()
|
|
19
|
-
|
|
20
|
-
for (const [filepath, raw] of Object.entries(this.dotenvFiles)) {
|
|
21
|
-
const environment = guessEnvironment(filepath)
|
|
22
|
-
const vault = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
23
|
-
|
|
24
|
-
let ciphertext = this.dotenvVaults[vault]
|
|
25
|
-
const dotenvKey = this.dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
|
|
26
|
-
|
|
27
|
-
const cleanRaw = removePersonal(raw)
|
|
28
|
-
|
|
29
|
-
if (!ciphertext || ciphertext.length === 0 || changed(ciphertext, cleanRaw, dotenvKey)) {
|
|
30
|
-
ciphertext = encrypt(cleanRaw, dotenvKey)
|
|
31
|
-
this.dotenvVaults[vault] = ciphertext
|
|
32
|
-
addedVaults.add(vault) // for info logging to user
|
|
33
|
-
|
|
34
|
-
addedDotenvFilenames.add(path.basename(filepath)) // for info logging to user
|
|
35
|
-
} else {
|
|
36
|
-
existingVaults.add(vault) // for info logging to user
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let vaultData = `#/-------------------.env.vault---------------------/
|
|
41
|
-
#/ cloud-agnostic vaulting standard /
|
|
42
|
-
#/ [how it works](https://dotenvx.com/env-vault) /
|
|
43
|
-
#/--------------------------------------------------/\n\n`
|
|
44
|
-
|
|
45
|
-
for (const [vault, value] of Object.entries(this.dotenvVaults)) {
|
|
46
|
-
const environment = vault.replace('DOTENV_VAULT_', '').toLowerCase()
|
|
47
|
-
vaultData += `# ${environment}\n`
|
|
48
|
-
vaultData += `${vault}="${value}"\n\n`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
dotenvVaultFile: vaultData,
|
|
53
|
-
addedVaults: [...addedVaults], // return set as array
|
|
54
|
-
existingVaults: [...existingVaults], // return set as array
|
|
55
|
-
addedDotenvFilenames: [...addedDotenvFilenames] // return set as array
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
module.exports = DotenvVault
|
package/src/lib/helpers/hash.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const diff = require('diff')
|
|
4
|
-
|
|
5
|
-
const Ls = require('./ls')
|
|
6
|
-
const VaultDecrypt = require('./vaultDecrypt')
|
|
7
|
-
|
|
8
|
-
const containsDirectory = require('./../helpers/containsDirectory')
|
|
9
|
-
const guessEnvironment = require('./../helpers/guessEnvironment')
|
|
10
|
-
const { getColor } = require('./../../shared/colors')
|
|
11
|
-
|
|
12
|
-
const ENCODING = 'utf8'
|
|
13
|
-
|
|
14
|
-
class Status {
|
|
15
|
-
constructor (directory = '.') {
|
|
16
|
-
this.directory = directory
|
|
17
|
-
this.changes = []
|
|
18
|
-
this.nochanges = []
|
|
19
|
-
this.untracked = [] // not tracked in .env.vault
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
run () {
|
|
23
|
-
// get list of .env files
|
|
24
|
-
const files = new Ls(this.directory).run()
|
|
25
|
-
// iterate over each one
|
|
26
|
-
for (const filename of files) {
|
|
27
|
-
// skip file if directory
|
|
28
|
-
if (containsDirectory(filename)) {
|
|
29
|
-
continue
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// skip file if .env.keys
|
|
33
|
-
if (filename.endsWith('.env.keys')) {
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// skip file if .env.vault
|
|
38
|
-
if (filename.endsWith('.env.vault')) {
|
|
39
|
-
continue
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// skip file if .env.example
|
|
43
|
-
if (filename.endsWith('.env.example')) {
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// skip file if *.previous
|
|
48
|
-
if (filename.endsWith('.previous')) {
|
|
49
|
-
continue
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const filepath = path.resolve(this.directory, filename)
|
|
53
|
-
|
|
54
|
-
const row = {}
|
|
55
|
-
row.filename = filename
|
|
56
|
-
row.filepath = filepath
|
|
57
|
-
row.environment = guessEnvironment(filepath)
|
|
58
|
-
|
|
59
|
-
// grab raw
|
|
60
|
-
row.raw = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
61
|
-
|
|
62
|
-
// grab decrypted
|
|
63
|
-
const { processedEnvs } = new VaultDecrypt(this.directory, row.environment).run()
|
|
64
|
-
const result = processedEnvs[0]
|
|
65
|
-
|
|
66
|
-
// handle warnings
|
|
67
|
-
row.decrypted = result.decrypted
|
|
68
|
-
if (result.warning) {
|
|
69
|
-
this.untracked.push(row)
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// differences
|
|
74
|
-
row.differences = diff.diffWords(row.decrypted, row.raw)
|
|
75
|
-
|
|
76
|
-
// any changes?
|
|
77
|
-
const hasChanges = this._hasChanges(row.differences)
|
|
78
|
-
|
|
79
|
-
if (hasChanges) {
|
|
80
|
-
row.coloredDiff = row.differences.map(this._colorizeDiff).join('')
|
|
81
|
-
this.changes.push(row)
|
|
82
|
-
} else {
|
|
83
|
-
this.nochanges.push(row)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
changes: this.changes,
|
|
89
|
-
nochanges: this.nochanges,
|
|
90
|
-
untracked: this.untracked
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
_colorizeDiff (part) {
|
|
95
|
-
// If the part was added, color it green
|
|
96
|
-
if (part.added) {
|
|
97
|
-
return getColor('green')(part.value)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// If the part was removed, color it red
|
|
101
|
-
if (part.removed) {
|
|
102
|
-
return getColor('red')(part.value)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// No color for unchanged parts
|
|
106
|
-
return part.value
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
_hasChanges (differences) {
|
|
110
|
-
return differences.some(part => part.added || part.removed)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = Status
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const dotenv = require('dotenv')
|
|
4
|
-
|
|
5
|
-
const ENCODING = 'utf8'
|
|
6
|
-
|
|
7
|
-
const libDecrypt = require('./../../lib/helpers/decrypt')
|
|
8
|
-
|
|
9
|
-
class VaultDecrypt {
|
|
10
|
-
constructor (directory = '.', environment) {
|
|
11
|
-
this.directory = directory
|
|
12
|
-
this.environment = environment
|
|
13
|
-
|
|
14
|
-
this.envKeysFilepath = path.resolve(this.directory, '.env.keys')
|
|
15
|
-
this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
|
|
16
|
-
|
|
17
|
-
this.processedEnvs = []
|
|
18
|
-
this.changedFilenames = new Set()
|
|
19
|
-
this.unchangedFilenames = new Set()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
run () {
|
|
23
|
-
if (!fs.existsSync(this.envVaultFilepath)) {
|
|
24
|
-
const code = 'MISSING_ENV_VAULT_FILE'
|
|
25
|
-
const message = `missing .env.vault (${this.envVaultFilepath})`
|
|
26
|
-
const help = `? generate one with [dotenvx ext vault encrypt ${this.directory}]`
|
|
27
|
-
|
|
28
|
-
const error = new Error(message)
|
|
29
|
-
error.code = code
|
|
30
|
-
error.help = help
|
|
31
|
-
throw error
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!fs.existsSync(this.envKeysFilepath)) {
|
|
35
|
-
const code = 'MISSING_ENV_KEYS_FILE'
|
|
36
|
-
const message = `missing .env.keys (${this.envKeysFilepath})`
|
|
37
|
-
const help = '? a .env.keys file must be present in order to decrypt your .env.vault contents to .env file(s)'
|
|
38
|
-
|
|
39
|
-
const error = new Error(message)
|
|
40
|
-
error.code = code
|
|
41
|
-
error.help = help
|
|
42
|
-
throw error
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const dotenvKeys = dotenv.configDotenv({ path: this.envKeysFilepath }).parsed
|
|
46
|
-
const dotenvVault = dotenv.configDotenv({ path: this.envVaultFilepath }).parsed
|
|
47
|
-
const environments = this._environments()
|
|
48
|
-
|
|
49
|
-
if (environments.length > 0) {
|
|
50
|
-
// iterate over the environments
|
|
51
|
-
for (const environment of environments) {
|
|
52
|
-
const value = dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
|
|
53
|
-
const row = this._processRow(value, dotenvVault, environment)
|
|
54
|
-
|
|
55
|
-
this.processedEnvs.push(row)
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
for (const [dotenvKey, value] of Object.entries(dotenvKeys)) {
|
|
59
|
-
const environment = dotenvKey.replace('DOTENV_KEY_', '').toLowerCase()
|
|
60
|
-
const row = this._processRow(value, dotenvVault, environment)
|
|
61
|
-
|
|
62
|
-
this.processedEnvs.push(row)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
processedEnvs: this.processedEnvs,
|
|
68
|
-
changedFilenames: [...this.changedFilenames],
|
|
69
|
-
unchangedFilenames: [...this.unchangedFilenames]
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
_processRow (value, dotenvVault, environment) {
|
|
74
|
-
environment = environment.toLowerCase() // important so we don't later write .env.DEVELOPMENT for example
|
|
75
|
-
const vaultKey = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
76
|
-
const ciphertext = dotenvVault[vaultKey] // attempt to find ciphertext
|
|
77
|
-
|
|
78
|
-
const row = {}
|
|
79
|
-
row.environment = environment
|
|
80
|
-
row.dotenvKey = value ? value.trim() : value
|
|
81
|
-
row.ciphertext = ciphertext
|
|
82
|
-
|
|
83
|
-
if (ciphertext && ciphertext.length >= 1) {
|
|
84
|
-
// Decrypt
|
|
85
|
-
const decrypted = libDecrypt(ciphertext, value.trim())
|
|
86
|
-
row.decrypted = decrypted
|
|
87
|
-
|
|
88
|
-
// envFilename
|
|
89
|
-
// replace _ with . to support filenames like .env.development.local
|
|
90
|
-
let envFilename = `.env.${environment.replace('_', '.')}`
|
|
91
|
-
if (environment === 'development') {
|
|
92
|
-
envFilename = '.env'
|
|
93
|
-
}
|
|
94
|
-
row.filename = envFilename
|
|
95
|
-
row.filepath = path.resolve(this.directory, envFilename)
|
|
96
|
-
|
|
97
|
-
// check if exists and is changing
|
|
98
|
-
if (fs.existsSync(row.filepath) && (fs.readFileSync(row.filepath, { encoding: ENCODING }).toString() === decrypted)) {
|
|
99
|
-
this.unchangedFilenames.add(envFilename)
|
|
100
|
-
} else {
|
|
101
|
-
row.shouldWrite = true
|
|
102
|
-
this.changedFilenames.add(envFilename)
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
const message = `${vaultKey} missing in .env.vault: ${this.envVaultFilepath}`
|
|
106
|
-
const warning = new Error(message)
|
|
107
|
-
row.warning = warning
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return row
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
_environments () {
|
|
114
|
-
if (this.environment === undefined) {
|
|
115
|
-
return []
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!Array.isArray(this.environment)) {
|
|
119
|
-
return [this.environment]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return this.environment
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = VaultDecrypt
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const dotenv = require('dotenv')
|
|
4
|
-
|
|
5
|
-
const DotenvKeys = require('./../helpers/dotenvKeys')
|
|
6
|
-
const DotenvVault = require('./../helpers/dotenvVault')
|
|
7
|
-
|
|
8
|
-
const ENCODING = 'utf8'
|
|
9
|
-
|
|
10
|
-
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
11
|
-
|
|
12
|
-
class VaultEncrypt {
|
|
13
|
-
constructor (directory = '.', envFile) {
|
|
14
|
-
this.directory = directory
|
|
15
|
-
this.envFile = envFile || findEnvFiles(directory)
|
|
16
|
-
// calculated
|
|
17
|
-
this.envKeysFilepath = path.resolve(this.directory, '.env.keys')
|
|
18
|
-
this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
run () {
|
|
22
|
-
if (this.envFile.length < 1) {
|
|
23
|
-
const code = 'MISSING_ENV_FILES'
|
|
24
|
-
const message = 'no .env* files found'
|
|
25
|
-
const help = '? add one with [echo "HELLO=World" > .env] and then run [dotenvx ext vault encrypt]'
|
|
26
|
-
|
|
27
|
-
const error = new Error(message)
|
|
28
|
-
error.code = code
|
|
29
|
-
error.help = help
|
|
30
|
-
throw error
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const parsedDotenvKeys = this._parsedDotenvKeys()
|
|
34
|
-
const parsedDotenvVaults = this._parsedDotenvVault()
|
|
35
|
-
const envFilepaths = this._envFilepaths()
|
|
36
|
-
|
|
37
|
-
// build filepaths to be passed to DotenvKeys
|
|
38
|
-
const uniqueEnvFilepaths = new Set()
|
|
39
|
-
for (const envFilepath of envFilepaths) {
|
|
40
|
-
const filepath = path.resolve(this.directory, envFilepath)
|
|
41
|
-
if (!fs.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 ext vault encrypt]`
|
|
45
|
-
|
|
46
|
-
const error = new Error(message)
|
|
47
|
-
error.code = code
|
|
48
|
-
error.help = help
|
|
49
|
-
throw error
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
uniqueEnvFilepaths.add(filepath)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// generate .env.keys string
|
|
56
|
-
const {
|
|
57
|
-
dotenvKeys,
|
|
58
|
-
dotenvKeysFile,
|
|
59
|
-
addedKeys,
|
|
60
|
-
existingKeys
|
|
61
|
-
} = new DotenvKeys([...uniqueEnvFilepaths], parsedDotenvKeys).run()
|
|
62
|
-
|
|
63
|
-
// build look up of .env filepaths and their raw content
|
|
64
|
-
const dotenvFiles = {}
|
|
65
|
-
for (const filepath of [...uniqueEnvFilepaths]) {
|
|
66
|
-
const raw = fs.readFileSync(filepath, ENCODING)
|
|
67
|
-
dotenvFiles[filepath] = raw
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// generate .env.vault string
|
|
71
|
-
const {
|
|
72
|
-
dotenvVaultFile,
|
|
73
|
-
addedVaults,
|
|
74
|
-
existingVaults,
|
|
75
|
-
addedDotenvFilenames
|
|
76
|
-
} = new DotenvVault(dotenvFiles, dotenvKeys, parsedDotenvVaults).run()
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
// from DotenvKeys
|
|
80
|
-
dotenvKeys,
|
|
81
|
-
dotenvKeysFile,
|
|
82
|
-
addedKeys,
|
|
83
|
-
existingKeys,
|
|
84
|
-
// from DotenvVault
|
|
85
|
-
dotenvVaultFile,
|
|
86
|
-
addedVaults,
|
|
87
|
-
existingVaults,
|
|
88
|
-
addedDotenvFilenames,
|
|
89
|
-
envFile: this.envFile
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
_envFilepaths () {
|
|
94
|
-
if (!Array.isArray(this.envFile)) {
|
|
95
|
-
return [this.envFile]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return this.envFile
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
_parsedDotenvKeys () {
|
|
102
|
-
const options = {
|
|
103
|
-
path: this.envKeysFilepath
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return dotenv.configDotenv(options).parsed
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
_parsedDotenvVault () {
|
|
110
|
-
const options = {
|
|
111
|
-
path: this.envVaultFilepath
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return dotenv.configDotenv(options).parsed
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
module.exports = VaultEncrypt
|