@dotenvx/dotenvx 1.19.3 → 1.20.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 +12 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/actions/decrypt.js +25 -30
- package/src/cli/actions/encrypt.js +29 -32
- package/src/cli/actions/set.js +24 -27
- package/src/cli/dotenvx.js +15 -8
- package/src/lib/helpers/catchAndLog.js +16 -0
- package/src/lib/helpers/decryptValue.js +4 -2
- package/src/lib/helpers/determineEnvs.js +65 -0
- package/src/lib/helpers/dotenvPrivateKeyNames.js +7 -0
- package/src/lib/helpers/findPrivateKey.js +33 -0
- package/src/lib/helpers/findPublicKey.js +33 -0
- package/src/lib/helpers/isEncrypted.js +1 -1
- package/src/lib/helpers/isFullyEncrypted.js +1 -1
- package/src/lib/main.js +0 -21
- package/src/lib/services/decrypt.js +86 -81
- package/src/lib/services/encrypt.js +164 -93
- package/src/lib/services/run.js +5 -89
- package/src/lib/services/sets.js +155 -72
- package/src/shared/colors.js +6 -2
- package/src/shared/logger.js +1 -1
- package/src/lib/helpers/findOrCreatePublicKey.js +0 -94
|
@@ -3,113 +3,114 @@ const path = require('path')
|
|
|
3
3
|
const dotenv = require('dotenv')
|
|
4
4
|
const picomatch = require('picomatch')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const TYPE_ENV_FILE = 'envFile'
|
|
7
|
+
|
|
7
8
|
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
9
|
+
const findPrivateKey = require('./../helpers/findPrivateKey')
|
|
8
10
|
const decryptValue = require('./../helpers/decryptValue')
|
|
9
11
|
const isEncrypted = require('./../helpers/isEncrypted')
|
|
10
12
|
const replace = require('./../helpers/replace')
|
|
13
|
+
const detectEncoding = require('./../helpers/detectEncoding')
|
|
14
|
+
const determineEnvs = require('./../helpers/determineEnvs')
|
|
11
15
|
|
|
12
16
|
class Decrypt {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* @param {string|string[]} [key]
|
|
16
|
-
* @param {string|string[]} [excludeKey]
|
|
17
|
-
**/
|
|
18
|
-
constructor (envFile = '.env', key = [], excludeKey = []) {
|
|
19
|
-
this.envFile = envFile
|
|
17
|
+
constructor (envs = [], key = [], excludeKey = []) {
|
|
18
|
+
this.envs = determineEnvs(envs, process.env)
|
|
20
19
|
this.key = key
|
|
21
20
|
this.excludeKey = excludeKey
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
this.processedEnvs = []
|
|
23
23
|
this.changedFilepaths = new Set()
|
|
24
24
|
this.unchangedFilepaths = new Set()
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
run () {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// example
|
|
29
|
+
// envs [
|
|
30
|
+
// { type: 'envFile', value: '.env' }
|
|
31
|
+
// ]
|
|
32
|
+
|
|
33
|
+
this.keys = this._keys()
|
|
30
34
|
const excludeKeys = this._excludeKeys()
|
|
31
|
-
const exclude = picomatch(excludeKeys)
|
|
32
|
-
const include = picomatch(keys, { ignore: excludeKeys })
|
|
33
|
-
|
|
34
|
-
for (const envFilepath of envFilepaths) {
|
|
35
|
-
const filepath = path.resolve(envFilepath)
|
|
36
|
-
|
|
37
|
-
const row = {}
|
|
38
|
-
row.keys = []
|
|
39
|
-
row.filepath = filepath
|
|
40
|
-
row.envFilepath = envFilepath
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// get the src
|
|
44
|
-
let src = fsx.readFileX(filepath)
|
|
45
|
-
|
|
46
|
-
// if DOTENV_PRIVATE_KEY_* already set in process.env then use it
|
|
47
|
-
const privateKey = smartDotenvPrivateKey(envFilepath)
|
|
48
|
-
row.privateKey = privateKey
|
|
49
|
-
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
50
|
-
|
|
51
|
-
// track possible changes
|
|
52
|
-
row.changed = false
|
|
53
|
-
|
|
54
|
-
// iterate over all non-encrypted values and encrypt them
|
|
55
|
-
const parsed = dotenv.parse(src)
|
|
56
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
57
|
-
// key excluded - don't decrypt it
|
|
58
|
-
if (exclude(key)) {
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// key effectively excluded (by not being in the list of includes) - don't encrypt it
|
|
63
|
-
if (keys.length > 0 && !include(key)) {
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const encrypted = isEncrypted(key, value)
|
|
68
|
-
if (encrypted) {
|
|
69
|
-
row.keys.push(key) // track key(s)
|
|
70
|
-
|
|
71
|
-
const decryptedValue = decryptValue(value, privateKey)
|
|
72
|
-
// once newSrc is built write it out
|
|
73
|
-
src = replace(src, key, decryptedValue)
|
|
74
|
-
|
|
75
|
-
row.changed = true // track change
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
35
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.changedFilepaths.add(envFilepath)
|
|
82
|
-
} else {
|
|
83
|
-
row.envSrc = src
|
|
84
|
-
this.unchangedFilepaths.add(envFilepath)
|
|
85
|
-
}
|
|
86
|
-
} catch (e) {
|
|
87
|
-
if (e.code === 'ENOENT') {
|
|
88
|
-
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
89
|
-
error.code = 'MISSING_ENV_FILE'
|
|
90
|
-
|
|
91
|
-
row.error = error
|
|
92
|
-
} else {
|
|
93
|
-
row.error = e
|
|
94
|
-
}
|
|
95
|
-
}
|
|
36
|
+
this.exclude = picomatch(excludeKeys)
|
|
37
|
+
this.include = picomatch(this.keys, { ignore: excludeKeys })
|
|
96
38
|
|
|
97
|
-
|
|
39
|
+
for (const env of this.envs) {
|
|
40
|
+
if (env.type === TYPE_ENV_FILE) {
|
|
41
|
+
this._decryptEnvFile(env.value)
|
|
42
|
+
}
|
|
98
43
|
}
|
|
99
44
|
|
|
100
45
|
return {
|
|
101
|
-
|
|
46
|
+
processedEnvs: this.processedEnvs,
|
|
102
47
|
changedFilepaths: [...this.changedFilepaths],
|
|
103
48
|
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
104
49
|
}
|
|
105
50
|
}
|
|
106
51
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
52
|
+
_decryptEnvFile (envFilepath) {
|
|
53
|
+
const row = {}
|
|
54
|
+
row.keys = []
|
|
55
|
+
row.type = TYPE_ENV_FILE
|
|
56
|
+
|
|
57
|
+
const filepath = path.resolve(envFilepath)
|
|
58
|
+
row.filepath = filepath
|
|
59
|
+
row.envFilepath = envFilepath
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const encoding = this._detectEncoding(filepath)
|
|
63
|
+
let envSrc = fsx.readFileX(filepath, { encoding })
|
|
64
|
+
const envParsed = dotenv.parse(envSrc)
|
|
65
|
+
|
|
66
|
+
const privateKey = findPrivateKey(envFilepath)
|
|
67
|
+
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
68
|
+
|
|
69
|
+
row.privateKey = privateKey
|
|
70
|
+
row.privateKeyName = privateKeyName
|
|
71
|
+
row.changed = false // track possible changes
|
|
72
|
+
|
|
73
|
+
for (const [key, value] of Object.entries(envParsed)) {
|
|
74
|
+
// key excluded - don't decrypt it
|
|
75
|
+
if (this.exclude(key)) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// key effectively excluded (by not being in the list of includes) - don't decrypt it
|
|
80
|
+
if (this.keys.length > 0 && !this.include(key)) {
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const encrypted = isEncrypted(value)
|
|
85
|
+
if (encrypted) {
|
|
86
|
+
row.keys.push(key) // track key(s)
|
|
87
|
+
|
|
88
|
+
const decryptedValue = decryptValue(value, privateKey)
|
|
89
|
+
// once newSrc is built write it out
|
|
90
|
+
envSrc = replace(envSrc, key, decryptedValue)
|
|
91
|
+
|
|
92
|
+
row.changed = true // track change
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
row.envSrc = envSrc
|
|
97
|
+
if (row.changed) {
|
|
98
|
+
this.changedFilepaths.add(envFilepath)
|
|
99
|
+
} else {
|
|
100
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
if (e.code === 'ENOENT') {
|
|
104
|
+
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
105
|
+
error.code = 'MISSING_ENV_FILE'
|
|
106
|
+
|
|
107
|
+
row.error = error
|
|
108
|
+
} else {
|
|
109
|
+
row.error = e
|
|
110
|
+
}
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
this.processedEnvs.push(row)
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
_keys () {
|
|
@@ -127,6 +128,10 @@ class Decrypt {
|
|
|
127
128
|
|
|
128
129
|
return this.excludeKey
|
|
129
130
|
}
|
|
131
|
+
|
|
132
|
+
_detectEncoding (filepath) {
|
|
133
|
+
return detectEncoding(filepath)
|
|
134
|
+
}
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
module.exports = Decrypt
|
|
@@ -3,127 +3,194 @@ const path = require('path')
|
|
|
3
3
|
const dotenv = require('dotenv')
|
|
4
4
|
const picomatch = require('picomatch')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const TYPE_ENV_FILE = 'envFile'
|
|
7
|
+
|
|
7
8
|
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
9
|
+
const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
|
|
8
10
|
const encryptValue = require('./../helpers/encryptValue')
|
|
9
11
|
const isEncrypted = require('./../helpers/isEncrypted')
|
|
10
|
-
const isPublicKey = require('./../helpers/isPublicKey')
|
|
11
12
|
const replace = require('./../helpers/replace')
|
|
13
|
+
const detectEncoding = require('./../helpers/detectEncoding')
|
|
14
|
+
const determineEnvs = require('./../helpers/determineEnvs')
|
|
15
|
+
const findPrivateKey = require('./../helpers/findPrivateKey')
|
|
16
|
+
const findPublicKey = require('./../helpers/findPublicKey')
|
|
17
|
+
const keyPair = require('./../helpers/keyPair')
|
|
18
|
+
const truncate = require('./../helpers/truncate')
|
|
19
|
+
const isPublicKey = require('./../helpers/isPublicKey')
|
|
12
20
|
|
|
13
21
|
class Encrypt {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* @param {string|string[]} [key]
|
|
17
|
-
* @param {string|string[]} [excludeKey]
|
|
18
|
-
**/
|
|
19
|
-
constructor (envFile = '.env', key = [], excludeKey = []) {
|
|
20
|
-
this.envFile = envFile
|
|
22
|
+
constructor (envs = [], key = [], excludeKey = []) {
|
|
23
|
+
this.envs = determineEnvs(envs, process.env)
|
|
21
24
|
this.key = key
|
|
22
25
|
this.excludeKey = excludeKey
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
this.processedEnvs = []
|
|
24
28
|
this.changedFilepaths = new Set()
|
|
25
29
|
this.unchangedFilepaths = new Set()
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
run () {
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// example
|
|
34
|
+
// envs [
|
|
35
|
+
// { type: 'envFile', value: '.env' }
|
|
36
|
+
// ]
|
|
37
|
+
|
|
38
|
+
this.keys = this._keys()
|
|
31
39
|
const excludeKeys = this._excludeKeys()
|
|
32
|
-
const exclude = picomatch(excludeKeys)
|
|
33
|
-
const include = picomatch(keys, { ignore: excludeKeys })
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
this.exclude = picomatch(excludeKeys)
|
|
42
|
+
this.include = picomatch(this.keys, { ignore: excludeKeys })
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
for (const env of this.envs) {
|
|
45
|
+
if (env.type === TYPE_ENV_FILE) {
|
|
46
|
+
this._encryptEnvFile(env.value)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
processedEnvs: this.processedEnvs,
|
|
52
|
+
changedFilepaths: [...this.changedFilepaths],
|
|
53
|
+
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
_encryptEnvFile (envFilepath) {
|
|
58
|
+
const row = {}
|
|
59
|
+
row.keys = []
|
|
60
|
+
row.type = TYPE_ENV_FILE
|
|
61
|
+
|
|
62
|
+
const filename = path.basename(envFilepath)
|
|
63
|
+
const filepath = path.resolve(envFilepath)
|
|
64
|
+
row.filepath = filepath
|
|
65
|
+
row.envFilepath = envFilepath
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const encoding = this._detectEncoding(filepath)
|
|
69
|
+
let envSrc = fsx.readFileX(filepath, { encoding })
|
|
70
|
+
const envParsed = dotenv.parse(envSrc)
|
|
71
|
+
|
|
72
|
+
let publicKey
|
|
73
|
+
let privateKey
|
|
74
|
+
|
|
75
|
+
const publicKeyName = guessPublicKeyName(envFilepath)
|
|
76
|
+
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
77
|
+
const existingPrivateKey = findPrivateKey(envFilepath)
|
|
78
|
+
const existingPublicKey = findPublicKey(envFilepath)
|
|
79
|
+
|
|
80
|
+
if (existingPrivateKey) {
|
|
81
|
+
const kp = keyPair(existingPrivateKey)
|
|
82
|
+
publicKey = kp.publicKey
|
|
83
|
+
privateKey = kp.privateKey
|
|
84
|
+
|
|
85
|
+
// if derivation doesn't match what's in the file (or preset in env)
|
|
86
|
+
if (existingPublicKey && existingPublicKey !== publicKey) {
|
|
87
|
+
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`)
|
|
88
|
+
error.code = 'INVALID_DOTENV_PRIVATE_KEY'
|
|
89
|
+
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
} else if (existingPublicKey) {
|
|
93
|
+
publicKey = existingPublicKey
|
|
94
|
+
} else {
|
|
95
|
+
// .env.keys
|
|
96
|
+
let keysSrc = ''
|
|
47
97
|
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
publicKey,
|
|
52
|
-
privateKey,
|
|
53
|
-
publicKeyAdded,
|
|
54
|
-
privateKeyAdded
|
|
55
|
-
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
56
|
-
|
|
57
|
-
// handle .env.keys write
|
|
58
|
-
fsx.writeFileX(envKeysFilepath, keysSrc)
|
|
98
|
+
if (fsx.existsSync(envKeysFilepath)) {
|
|
99
|
+
keysSrc = fsx.readFileX(envKeysFilepath)
|
|
100
|
+
}
|
|
59
101
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
67
|
-
|
|
68
|
-
// iterate over all non-encrypted values and encrypt them
|
|
69
|
-
const parsed = dotenv.parse(src)
|
|
70
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
71
|
-
// key excluded - don't encrypt it
|
|
72
|
-
if (exclude(key)) {
|
|
73
|
-
continue
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// key effectively excluded (by not being in the list of includes) - don't encrypt it
|
|
77
|
-
if (keys.length > 0 && !include(key)) {
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const encrypted = isEncrypted(key, value) || isPublicKey(key, value)
|
|
82
|
-
if (!encrypted) {
|
|
83
|
-
row.keys.push(key) // track key(s)
|
|
84
|
-
|
|
85
|
-
const encryptedValue = encryptValue(value, publicKey)
|
|
86
|
-
// once newSrc is built write it out
|
|
87
|
-
src = replace(src, key, encryptedValue)
|
|
88
|
-
|
|
89
|
-
row.changed = true // track change
|
|
90
|
-
}
|
|
102
|
+
// preserve shebang
|
|
103
|
+
const [firstLine, ...remainingLines] = envSrc.split('\n')
|
|
104
|
+
let firstLinePreserved = ''
|
|
105
|
+
if (firstLine.startsWith('#!')) {
|
|
106
|
+
firstLinePreserved = firstLine + '\n'
|
|
107
|
+
envSrc = remainingLines.join('\n')
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
const kp = keyPair() // generates a fresh keypair in memory
|
|
111
|
+
publicKey = kp.publicKey
|
|
112
|
+
privateKey = kp.privateKey
|
|
113
|
+
|
|
114
|
+
// publicKey
|
|
115
|
+
const prependPublicKey = [
|
|
116
|
+
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
|
|
117
|
+
'#/ public-key encryption for .env files /',
|
|
118
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
119
|
+
'#/----------------------------------------------------------/',
|
|
120
|
+
`${publicKeyName}="${publicKey}"`,
|
|
121
|
+
'',
|
|
122
|
+
`# ${filename}`
|
|
123
|
+
].join('\n')
|
|
124
|
+
|
|
125
|
+
// privateKey
|
|
126
|
+
const firstTimeKeysSrc = [
|
|
127
|
+
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
|
|
128
|
+
'#/ private decryption keys. DO NOT commit to source control /',
|
|
129
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
130
|
+
'#/----------------------------------------------------------/'
|
|
131
|
+
].join('\n')
|
|
132
|
+
const appendPrivateKey = [
|
|
133
|
+
`# ${filename}`,
|
|
134
|
+
`${privateKeyName}="${privateKey}"`,
|
|
135
|
+
''
|
|
136
|
+
].join('\n')
|
|
137
|
+
|
|
138
|
+
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
|
|
139
|
+
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
|
|
140
|
+
keysSrc = `${keysSrc}\n${appendPrivateKey}`
|
|
141
|
+
|
|
142
|
+
// write to .env.keys
|
|
143
|
+
fsx.writeFileX(envKeysFilepath, keysSrc)
|
|
144
|
+
|
|
145
|
+
row.privateKeyAdded = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
row.publicKey = publicKey
|
|
149
|
+
row.privateKey = privateKey
|
|
150
|
+
row.privateKeyName = privateKeyName
|
|
151
|
+
|
|
152
|
+
// iterate over all non-encrypted values and encrypt them
|
|
153
|
+
for (const [key, value] of Object.entries(envParsed)) {
|
|
154
|
+
// key excluded - don't encrypt it
|
|
155
|
+
if (this.exclude(key)) {
|
|
156
|
+
continue
|
|
99
157
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
row.error = error
|
|
106
|
-
} else {
|
|
107
|
-
row.error = e
|
|
158
|
+
|
|
159
|
+
// key effectively excluded (by not being in the list of includes) - don't encrypt it
|
|
160
|
+
if (this.keys.length > 0 && !this.include(key)) {
|
|
161
|
+
continue
|
|
108
162
|
}
|
|
109
|
-
}
|
|
110
163
|
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
const encrypted = isEncrypted(value) || isPublicKey(key, value)
|
|
165
|
+
if (!encrypted) {
|
|
166
|
+
row.keys.push(key) // track key(s)
|
|
113
167
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
const encryptedValue = encryptValue(value, publicKey)
|
|
169
|
+
// once newSrc is built write it out
|
|
170
|
+
envSrc = replace(envSrc, key, encryptedValue)
|
|
171
|
+
|
|
172
|
+
row.changed = true // track change
|
|
173
|
+
}
|
|
174
|
+
}
|
|
120
175
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
176
|
+
row.envSrc = envSrc
|
|
177
|
+
if (row.changed) {
|
|
178
|
+
this.changedFilepaths.add(envFilepath)
|
|
179
|
+
} else {
|
|
180
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e.code === 'ENOENT') {
|
|
184
|
+
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
185
|
+
error.code = 'MISSING_ENV_FILE'
|
|
186
|
+
|
|
187
|
+
row.error = error
|
|
188
|
+
} else {
|
|
189
|
+
row.error = e
|
|
190
|
+
}
|
|
124
191
|
}
|
|
125
192
|
|
|
126
|
-
|
|
193
|
+
this.processedEnvs.push(row)
|
|
127
194
|
}
|
|
128
195
|
|
|
129
196
|
_keys () {
|
|
@@ -141,6 +208,10 @@ class Encrypt {
|
|
|
141
208
|
|
|
142
209
|
return this.excludeKey
|
|
143
210
|
}
|
|
211
|
+
|
|
212
|
+
_detectEncoding (filepath) {
|
|
213
|
+
return detectEncoding(filepath)
|
|
214
|
+
}
|
|
144
215
|
}
|
|
145
216
|
|
|
146
217
|
module.exports = Encrypt
|
package/src/lib/services/run.js
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const dotenv = require('dotenv')
|
|
4
|
-
const childProcess = require('child_process')
|
|
5
4
|
|
|
6
5
|
const TYPE_ENV = 'env'
|
|
7
6
|
const TYPE_ENV_FILE = 'envFile'
|
|
8
7
|
const TYPE_ENV_VAULT_FILE = 'envVaultFile'
|
|
9
|
-
const DEFAULT_ENVS = [{ type: TYPE_ENV_FILE, value: '.env' }]
|
|
10
|
-
const DEFAULT_ENV_VAULTS = [{ type: TYPE_ENV_VAULT_FILE, value: '.env.vault' }]
|
|
11
8
|
|
|
12
9
|
const inject = require('./../helpers/inject')
|
|
13
10
|
const decrypt = require('./../helpers/decrypt')
|
|
14
11
|
const parseDecryptEvalExpand = require('./../helpers/parseDecryptEvalExpand')
|
|
15
12
|
const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
|
|
16
|
-
const guessPrivateKeyFilename = require('./../helpers/guessPrivateKeyFilename')
|
|
17
|
-
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
18
13
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
19
|
-
|
|
20
|
-
const
|
|
14
|
+
const findPrivateKey = require('./../helpers/findPrivateKey')
|
|
15
|
+
const determineEnvs = require('./../helpers/determineEnvs')
|
|
21
16
|
|
|
22
17
|
class Run {
|
|
23
18
|
constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env) {
|
|
24
|
-
this.
|
|
25
|
-
this.envs = this._determineEnvs(envs, DOTENV_KEY)
|
|
19
|
+
this.envs = determineEnvs(envs, processEnv, DOTENV_KEY)
|
|
26
20
|
this.overload = overload
|
|
27
21
|
this.DOTENV_KEY = DOTENV_KEY
|
|
28
22
|
this.processEnv = processEnv
|
|
@@ -43,7 +37,7 @@ class Run {
|
|
|
43
37
|
// ]
|
|
44
38
|
|
|
45
39
|
for (const env of this.envs) {
|
|
46
|
-
if (env.type === TYPE_ENV_VAULT_FILE) {
|
|
40
|
+
if (env.type === TYPE_ENV_VAULT_FILE) { // deprecate someday - for deprecated .env.vault files
|
|
47
41
|
this._injectEnvVaultFile(env.value)
|
|
48
42
|
} else if (env.type === TYPE_ENV_FILE) {
|
|
49
43
|
this._injectEnvFile(env.value)
|
|
@@ -96,7 +90,7 @@ class Run {
|
|
|
96
90
|
const src = fsx.readFileX(filepath, { encoding })
|
|
97
91
|
this.readableFilepaths.add(envFilepath)
|
|
98
92
|
|
|
99
|
-
const privateKey =
|
|
93
|
+
const privateKey = findPrivateKey(envFilepath)
|
|
100
94
|
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
|
|
101
95
|
row.parsed = parsed
|
|
102
96
|
row.warnings = warnings
|
|
@@ -189,61 +183,6 @@ class Run {
|
|
|
189
183
|
return inject(clonedProcessEnv, parsed, overload, processEnv)
|
|
190
184
|
}
|
|
191
185
|
|
|
192
|
-
_determineEnvsFromDotenvPrivateKey () {
|
|
193
|
-
const envs = []
|
|
194
|
-
|
|
195
|
-
for (const privateKeyName of this.dotenvPrivateKeyNames) {
|
|
196
|
-
const filename = guessPrivateKeyFilename(privateKeyName)
|
|
197
|
-
envs.push({ type: TYPE_ENV_FILE, value: filename })
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return envs
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
_determineEnvs (envs = [], DOTENV_KEY = '') {
|
|
204
|
-
if (!envs || envs.length <= 0) {
|
|
205
|
-
// if process.env.DOTENV_PRIVATE_KEY or process.env.DOTENV_PRIVATE_KEY_${environment} is set, assume inline encryption methodology
|
|
206
|
-
if (this.dotenvPrivateKeyNames.length > 0) {
|
|
207
|
-
return this._determineEnvsFromDotenvPrivateKey()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (DOTENV_KEY.length > 0) {
|
|
211
|
-
// if DOTENV_KEY is set then default to look for .env.vault file
|
|
212
|
-
return DEFAULT_ENV_VAULTS
|
|
213
|
-
} else {
|
|
214
|
-
return DEFAULT_ENVS // default to .env file expectation
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
let fileAlreadySpecified = false // can be .env or .env.vault type
|
|
218
|
-
|
|
219
|
-
for (const env of envs) {
|
|
220
|
-
// if DOTENV_KEY set then we are checking if a .env.vault file is already specified
|
|
221
|
-
if (DOTENV_KEY.length > 0 && env.type === TYPE_ENV_VAULT_FILE) {
|
|
222
|
-
fileAlreadySpecified = true
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// if DOTENV_KEY not set then we are checking if a .env file is already specified
|
|
226
|
-
if (DOTENV_KEY.length <= 0 && env.type === TYPE_ENV_FILE) {
|
|
227
|
-
fileAlreadySpecified = true
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// return early since envs array objects already contain 1 .env.vault or .env file
|
|
232
|
-
if (fileAlreadySpecified) {
|
|
233
|
-
return envs
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// no .env.vault or .env file specified as a flag so we assume either .env.vault (if dotenv key is set) or a .env file
|
|
237
|
-
if (DOTENV_KEY.length > 0) {
|
|
238
|
-
// if DOTENV_KEY is set then default to look for .env.vault file
|
|
239
|
-
return [...DEFAULT_ENV_VAULTS, ...envs]
|
|
240
|
-
} else {
|
|
241
|
-
// if no DOTENV_KEY then default to look for .env file
|
|
242
|
-
return [...DEFAULT_ENVS, ...envs]
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
186
|
// handle scenario for comma separated keys - for use with key rotation
|
|
248
187
|
// example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
|
|
249
188
|
_dotenvKeys () {
|
|
@@ -271,29 +210,6 @@ class Run {
|
|
|
271
210
|
|
|
272
211
|
return decrypt(ciphertext, dotenvKey)
|
|
273
212
|
}
|
|
274
|
-
|
|
275
|
-
_determinePrivateKey (envFilepath) {
|
|
276
|
-
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
277
|
-
|
|
278
|
-
let privateKey
|
|
279
|
-
try {
|
|
280
|
-
// if installed as sibling module
|
|
281
|
-
const projectRoot = path.resolve(process.cwd())
|
|
282
|
-
const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] })
|
|
283
|
-
const { keypair } = require(dotenvxProPath)
|
|
284
|
-
privateKey = keypair(envFilepath, privateKeyName)
|
|
285
|
-
} catch (_e) {
|
|
286
|
-
try {
|
|
287
|
-
// if installed as binary cli
|
|
288
|
-
privateKey = childProcess.execSync(`dotenvx-pro keypair ${privateKeyName} -f ${envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
|
|
289
|
-
} catch (_e) {
|
|
290
|
-
// fallback to local KeyPair - smart enough to handle process.env, .env.keys, etc
|
|
291
|
-
privateKey = new Keypair(envFilepath, privateKeyName).run()
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return privateKey
|
|
296
|
-
}
|
|
297
213
|
}
|
|
298
214
|
|
|
299
215
|
module.exports = Run
|