@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.
@@ -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 findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
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, envFile = '.env', encrypt = true) {
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.processedEnvFiles = []
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
- const envFilepaths = this._envFilepaths()
25
- for (const envFilepath of envFilepaths) {
26
- const filepath = path.resolve(envFilepath)
27
-
28
- const row = {}
29
- row.key = this.key
30
- row.value = this.value
31
- row.filepath = filepath
32
- row.envFilepath = envFilepath
33
- row.changed = false
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
- if (value !== row.originalValue) {
67
- row.envSrc = replace(src, this.key, value)
45
+ return {
46
+ processedEnvs: this.processedEnvs,
47
+ changedFilepaths: [...this.changedFilepaths],
48
+ unchangedFilepaths: [...this.unchangedFilepaths]
49
+ }
50
+ }
68
51
 
69
- this.changedFilepaths.add(envFilepath)
70
- row.changed = true
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
- row.envSrc = src
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
- this.unchangedFilepaths.add(envFilepath)
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
- row.error = error
82
- } else {
83
- row.error = e
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.processedEnvFiles.push(row)
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
- return {
91
- processedEnvFiles: this.processedEnvFiles,
92
- changedFilepaths: [...this.changedFilepaths],
93
- unchangedFilepaths: [...this.unchangedFilepaths]
94
- }
181
+ this.processedEnvs.push(row)
95
182
  }
96
183
 
97
- _envFilepaths () {
98
- if (!Array.isArray(this.envFile)) {
99
- return [this.envFile]
100
- }
101
-
102
- return this.envFile
184
+ _detectEncoding (filepath) {
185
+ return detectEncoding(filepath)
103
186
  }
104
187
  }
105
188
 
@@ -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) {
@@ -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('blue')
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