@dotenvx/dotenvx 1.19.2 → 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 +18 -1
- package/README.md +12 -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/helpers/replace.js +6 -0
- 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
package/src/lib/services/sets.js
CHANGED
|
@@ -1,105 +1,188 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
|
|
4
3
|
const dotenv = require('dotenv')
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
const TYPE_ENV_FILE = 'envFile'
|
|
6
|
+
|
|
7
7
|
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
8
|
+
const guessPublicKeyName = require('./../helpers/guessPublicKeyName')
|
|
8
9
|
const encryptValue = require('./../helpers/encryptValue')
|
|
10
|
+
const decryptValue = require('./../helpers/decryptValue')
|
|
9
11
|
const replace = require('./../helpers/replace')
|
|
12
|
+
const detectEncoding = require('./../helpers/detectEncoding')
|
|
13
|
+
const determineEnvs = require('./../helpers/determineEnvs')
|
|
14
|
+
const findPrivateKey = require('./../helpers/findPrivateKey')
|
|
15
|
+
const findPublicKey = require('./../helpers/findPublicKey')
|
|
16
|
+
const keyPair = require('./../helpers/keyPair')
|
|
17
|
+
const truncate = require('./../helpers/truncate')
|
|
18
|
+
const isEncrypted = require('./../helpers/isEncrypted')
|
|
10
19
|
|
|
11
20
|
class Sets {
|
|
12
|
-
constructor (key, value,
|
|
21
|
+
constructor (key, value, envs = [], encrypt = true) {
|
|
22
|
+
this.envs = determineEnvs(envs, process.env)
|
|
13
23
|
this.key = key
|
|
14
24
|
this.value = value
|
|
15
|
-
this.envFile = envFile
|
|
16
25
|
this.encrypt = encrypt
|
|
17
26
|
|
|
18
|
-
this.
|
|
27
|
+
this.processedEnvs = []
|
|
19
28
|
this.changedFilepaths = new Set()
|
|
20
29
|
this.unchangedFilepaths = new Set()
|
|
30
|
+
this.readableFilepaths = new Set()
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
run () {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
let value = this.value
|
|
37
|
-
let src = fsx.readFileX(filepath)
|
|
38
|
-
row.originalValue = dotenv.parse(src)[row.key] || null
|
|
39
|
-
|
|
40
|
-
if (this.encrypt) {
|
|
41
|
-
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
42
|
-
const {
|
|
43
|
-
envSrc,
|
|
44
|
-
keysSrc,
|
|
45
|
-
publicKey,
|
|
46
|
-
privateKey,
|
|
47
|
-
publicKeyAdded,
|
|
48
|
-
privateKeyAdded
|
|
49
|
-
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
50
|
-
|
|
51
|
-
// handle .env.keys write
|
|
52
|
-
fsx.writeFileX(envKeysFilepath, keysSrc)
|
|
53
|
-
|
|
54
|
-
src = envSrc // src was potentially modified by findOrCreatePublicKey so we set it again here
|
|
55
|
-
|
|
56
|
-
value = encryptValue(value, publicKey)
|
|
57
|
-
|
|
58
|
-
row.changed = publicKeyAdded // track change
|
|
59
|
-
row.encryptedValue = value
|
|
60
|
-
row.publicKey = publicKey
|
|
61
|
-
row.privateKey = privateKey
|
|
62
|
-
row.privateKeyAdded = privateKeyAdded
|
|
63
|
-
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
64
|
-
}
|
|
34
|
+
// example
|
|
35
|
+
// envs [
|
|
36
|
+
// { type: 'envFile', value: '.env' }
|
|
37
|
+
// ]
|
|
38
|
+
|
|
39
|
+
for (const env of this.envs) {
|
|
40
|
+
if (env.type === TYPE_ENV_FILE) {
|
|
41
|
+
this._setEnvFile(env.value)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
65
44
|
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
return {
|
|
46
|
+
processedEnvs: this.processedEnvs,
|
|
47
|
+
changedFilepaths: [...this.changedFilepaths],
|
|
48
|
+
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
_setEnvFile (envFilepath) {
|
|
53
|
+
const row = {}
|
|
54
|
+
row.key = this.key || null
|
|
55
|
+
row.value = this.value || null
|
|
56
|
+
row.type = TYPE_ENV_FILE
|
|
57
|
+
|
|
58
|
+
const filename = path.basename(envFilepath)
|
|
59
|
+
const filepath = path.resolve(envFilepath)
|
|
60
|
+
row.filepath = filepath
|
|
61
|
+
row.envFilepath = envFilepath
|
|
62
|
+
row.changed = false
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const encoding = this._detectEncoding(filepath)
|
|
66
|
+
let envSrc = fsx.readFileX(filepath, { encoding })
|
|
67
|
+
const envParsed = dotenv.parse(envSrc)
|
|
68
|
+
row.originalValue = envParsed[row.key] || null
|
|
69
|
+
const wasPlainText = !isEncrypted(row.originalValue)
|
|
70
|
+
this.readableFilepaths.add(envFilepath)
|
|
71
|
+
|
|
72
|
+
if (this.encrypt) {
|
|
73
|
+
let publicKey
|
|
74
|
+
let privateKey
|
|
75
|
+
|
|
76
|
+
const publicKeyName = guessPublicKeyName(envFilepath)
|
|
77
|
+
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
78
|
+
const existingPrivateKey = findPrivateKey(envFilepath)
|
|
79
|
+
const existingPublicKey = findPublicKey(envFilepath)
|
|
80
|
+
|
|
81
|
+
if (existingPrivateKey) {
|
|
82
|
+
const kp = keyPair(existingPrivateKey)
|
|
83
|
+
publicKey = kp.publicKey
|
|
84
|
+
privateKey = kp.privateKey
|
|
85
|
+
|
|
86
|
+
if (row.originalValue) {
|
|
87
|
+
row.originalValue = decryptValue(row.originalValue, privateKey)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// if derivation doesn't match what's in the file (or preset in env)
|
|
91
|
+
if (existingPublicKey && existingPublicKey !== publicKey) {
|
|
92
|
+
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`)
|
|
93
|
+
error.code = 'INVALID_DOTENV_PRIVATE_KEY'
|
|
94
|
+
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})`
|
|
95
|
+
throw error
|
|
96
|
+
}
|
|
97
|
+
} else if (existingPublicKey) {
|
|
98
|
+
publicKey = existingPublicKey
|
|
71
99
|
} else {
|
|
72
|
-
|
|
100
|
+
// .env.keys
|
|
101
|
+
let keysSrc = ''
|
|
102
|
+
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
103
|
+
if (fsx.existsSync(envKeysFilepath)) {
|
|
104
|
+
keysSrc = fsx.readFileX(envKeysFilepath)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// preserve shebang
|
|
108
|
+
const [firstLine, ...remainingLines] = envSrc.split('\n')
|
|
109
|
+
let firstLinePreserved = ''
|
|
110
|
+
if (firstLine.startsWith('#!')) {
|
|
111
|
+
firstLinePreserved = firstLine + '\n'
|
|
112
|
+
envSrc = remainingLines.join('\n')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const kp = keyPair() // generates a fresh keypair in memory
|
|
116
|
+
publicKey = kp.publicKey
|
|
117
|
+
privateKey = kp.privateKey
|
|
118
|
+
|
|
119
|
+
// publicKey
|
|
120
|
+
const prependPublicKey = [
|
|
121
|
+
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
|
|
122
|
+
'#/ public-key encryption for .env files /',
|
|
123
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
124
|
+
'#/----------------------------------------------------------/',
|
|
125
|
+
`${publicKeyName}="${publicKey}"`,
|
|
126
|
+
'',
|
|
127
|
+
`# ${filename}`
|
|
128
|
+
].join('\n')
|
|
129
|
+
|
|
130
|
+
// privateKey
|
|
131
|
+
const firstTimeKeysSrc = [
|
|
132
|
+
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
|
|
133
|
+
'#/ private decryption keys. DO NOT commit to source control /',
|
|
134
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
135
|
+
'#/----------------------------------------------------------/'
|
|
136
|
+
].join('\n')
|
|
137
|
+
const appendPrivateKey = [
|
|
138
|
+
`# ${filename}`,
|
|
139
|
+
`${privateKeyName}="${privateKey}"`,
|
|
140
|
+
''
|
|
141
|
+
].join('\n')
|
|
142
|
+
|
|
143
|
+
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
|
|
144
|
+
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
|
|
145
|
+
keysSrc = `${keysSrc}\n${appendPrivateKey}`
|
|
146
|
+
|
|
147
|
+
// write to .env.keys
|
|
148
|
+
fsx.writeFileX(envKeysFilepath, keysSrc)
|
|
73
149
|
|
|
74
|
-
|
|
150
|
+
row.privateKeyAdded = true
|
|
75
151
|
}
|
|
76
|
-
} catch (e) {
|
|
77
|
-
if (e.code === 'ENOENT') {
|
|
78
|
-
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
79
|
-
error.code = 'MISSING_ENV_FILE'
|
|
80
152
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
153
|
+
row.publicKey = publicKey
|
|
154
|
+
row.privateKey = privateKey
|
|
155
|
+
row.encryptedValue = encryptValue(this.value, publicKey)
|
|
156
|
+
row.privateKeyName = privateKeyName
|
|
85
157
|
}
|
|
86
158
|
|
|
87
|
-
this.
|
|
159
|
+
const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt
|
|
160
|
+
const valueChanged = this.value !== row.originalValue
|
|
161
|
+
if (goingFromPlainTextToEncrypted || valueChanged) {
|
|
162
|
+
row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value)
|
|
163
|
+
this.changedFilepaths.add(envFilepath)
|
|
164
|
+
row.changed = true
|
|
165
|
+
} else {
|
|
166
|
+
row.envSrc = envSrc
|
|
167
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
168
|
+
row.changed = false
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
if (e.code === 'ENOENT') {
|
|
172
|
+
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
173
|
+
error.code = 'MISSING_ENV_FILE'
|
|
174
|
+
|
|
175
|
+
row.error = error
|
|
176
|
+
} else {
|
|
177
|
+
row.error = e
|
|
178
|
+
}
|
|
88
179
|
}
|
|
89
180
|
|
|
90
|
-
|
|
91
|
-
processedEnvFiles: this.processedEnvFiles,
|
|
92
|
-
changedFilepaths: [...this.changedFilepaths],
|
|
93
|
-
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
94
|
-
}
|
|
181
|
+
this.processedEnvs.push(row)
|
|
95
182
|
}
|
|
96
183
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return [this.envFile]
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return this.envFile
|
|
184
|
+
_detectEncoding (filepath) {
|
|
185
|
+
return detectEncoding(filepath)
|
|
103
186
|
}
|
|
104
187
|
}
|
|
105
188
|
|
package/src/shared/colors.js
CHANGED
|
@@ -7,7 +7,9 @@ const colors16 = new Map([
|
|
|
7
7
|
['olive', 33],
|
|
8
8
|
['orangered', 31], // mapped to red
|
|
9
9
|
['plum', 35], // mapped to magenta
|
|
10
|
-
['red', 31]
|
|
10
|
+
['red', 31],
|
|
11
|
+
['electricblue', 36],
|
|
12
|
+
['dodgerblue', 36]
|
|
11
13
|
])
|
|
12
14
|
|
|
13
15
|
const colors256 = new Map([
|
|
@@ -17,7 +19,9 @@ const colors256 = new Map([
|
|
|
17
19
|
['olive', 142],
|
|
18
20
|
['orangered', 202],
|
|
19
21
|
['plum', 182],
|
|
20
|
-
['red', 196]
|
|
22
|
+
['red', 196],
|
|
23
|
+
['electricblue', 45],
|
|
24
|
+
['dodgerblue', 33]
|
|
21
25
|
])
|
|
22
26
|
|
|
23
27
|
function getColor (color) {
|
package/src/shared/logger.js
CHANGED
|
@@ -28,7 +28,7 @@ const error = (m) => bold(getColor('red')(m))
|
|
|
28
28
|
const warn = getColor('orangered')
|
|
29
29
|
const success = getColor('green')
|
|
30
30
|
const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
|
|
31
|
-
const help = getColor('
|
|
31
|
+
const help = getColor('dodgerblue')
|
|
32
32
|
const help2 = getColor('gray')
|
|
33
33
|
const verbose = getColor('plum')
|
|
34
34
|
const debug = getColor('plum')
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
const fsx = require('./fsx')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const dotenv = require('dotenv')
|
|
4
|
-
|
|
5
|
-
const keyPair = require('./keyPair')
|
|
6
|
-
const guessPublicKeyName = require('./guessPublicKeyName')
|
|
7
|
-
const guessPrivateKeyName = require('./guessPrivateKeyName')
|
|
8
|
-
|
|
9
|
-
function findOrCreatePublicKey (envFilepath, envKeysFilepath) {
|
|
10
|
-
// filename
|
|
11
|
-
const filename = path.basename(envFilepath)
|
|
12
|
-
const publicKeyName = guessPublicKeyName(envFilepath)
|
|
13
|
-
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
14
|
-
|
|
15
|
-
// src
|
|
16
|
-
let envSrc = fsx.readFileX(envFilepath)
|
|
17
|
-
let keysSrc = ''
|
|
18
|
-
if (fsx.existsSync(envKeysFilepath)) {
|
|
19
|
-
keysSrc = fsx.readFileX(envKeysFilepath)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// parsed
|
|
23
|
-
const envParsed = dotenv.parse(envSrc)
|
|
24
|
-
const keysParsed = dotenv.parse(keysSrc)
|
|
25
|
-
const existingPublicKey = envParsed[publicKeyName]
|
|
26
|
-
const existingPrivateKey = keysParsed[privateKeyName]
|
|
27
|
-
|
|
28
|
-
// if DOTENV_PUBLIC_KEY_${environment} already present then go no further
|
|
29
|
-
if (existingPublicKey && existingPublicKey.length > 0) {
|
|
30
|
-
return {
|
|
31
|
-
envSrc,
|
|
32
|
-
keysSrc,
|
|
33
|
-
publicKey: existingPublicKey,
|
|
34
|
-
privateKey: existingPrivateKey,
|
|
35
|
-
publicKeyAdded: false,
|
|
36
|
-
privateKeyAdded: false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// preserve shebang
|
|
41
|
-
const [firstLine, ...remainingLines] = envSrc.split('\n')
|
|
42
|
-
let firstLinePreserved = ''
|
|
43
|
-
if (firstLine.startsWith('#!')) {
|
|
44
|
-
firstLinePreserved = firstLine + '\n'
|
|
45
|
-
envSrc = remainingLines.join('\n')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// generate key pair
|
|
49
|
-
const { publicKey, privateKey } = keyPair(existingPrivateKey)
|
|
50
|
-
|
|
51
|
-
// publicKey
|
|
52
|
-
const prependPublicKey = [
|
|
53
|
-
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
|
|
54
|
-
'#/ public-key encryption for .env files /',
|
|
55
|
-
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
56
|
-
'#/----------------------------------------------------------/',
|
|
57
|
-
`${publicKeyName}="${publicKey}"`,
|
|
58
|
-
'',
|
|
59
|
-
`# ${filename}`
|
|
60
|
-
].join('\n')
|
|
61
|
-
|
|
62
|
-
// privateKey
|
|
63
|
-
const firstTimeKeysSrc = [
|
|
64
|
-
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
|
|
65
|
-
'#/ private decryption keys. DO NOT commit to source control /',
|
|
66
|
-
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
67
|
-
'#/----------------------------------------------------------/'
|
|
68
|
-
].join('\n')
|
|
69
|
-
const appendPrivateKey = [
|
|
70
|
-
`# ${filename}`,
|
|
71
|
-
`${privateKeyName}="${privateKey}"`,
|
|
72
|
-
''
|
|
73
|
-
].join('\n')
|
|
74
|
-
|
|
75
|
-
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}`
|
|
76
|
-
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
|
|
77
|
-
|
|
78
|
-
let privateKeyAdded = false
|
|
79
|
-
if (!existingPrivateKey) {
|
|
80
|
-
keysSrc = `${keysSrc}\n${appendPrivateKey}`
|
|
81
|
-
privateKeyAdded = true
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
envSrc,
|
|
86
|
-
keysSrc,
|
|
87
|
-
publicKey,
|
|
88
|
-
privateKey,
|
|
89
|
-
publicKeyAdded: true,
|
|
90
|
-
privateKeyAdded
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
module.exports = findOrCreatePublicKey
|