@dotenvx/dotenvx 1.8.0 → 1.9.1
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 +24 -2
- package/README.md +14 -14
- package/package.json +1 -2
- package/src/cli/actions/decrypt.js +2 -2
- package/src/cli/actions/encrypt.js +2 -2
- package/src/cli/actions/ext/genexample.js +0 -3
- package/src/cli/dotenvx.js +3 -42
- package/src/lib/helpers/decryptValue.js +30 -24
- package/src/lib/helpers/parseDecryptEvalExpand.js +21 -25
- package/src/lib/helpers/truncate.js +6 -2
- package/src/lib/main.js +4 -4
- package/src/lib/services/decrypt.js +29 -10
- package/src/lib/services/encrypt.js +31 -14
- package/src/lib/services/genexample.js +42 -24
- package/src/shared/logger.js +64 -25
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,33 @@
|
|
|
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.9.1...main)
|
|
6
|
+
|
|
7
|
+
## 1.9.1
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* warn if private key is missing or blank ([#349](https://github.com/dotenvx/dotenvx/pull/349))
|
|
12
|
+
|
|
13
|
+
## 1.9.0
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* add `--exclude-key` (`-ek`) option to `dotenvx encrypt` and `dotenvx decrypt` ([#344](https://github.com/dotenvx/dotenvx/pull/344))
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
* preserve comments and spacing on first-time generation of .env.example file ([#346](https://github.com/dotenvx/dotenvx/pull/346))
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
* removed `winston` - logger simplified to use `console.log` going forward ([#347](https://github.com/dotenvx/dotenvx/pull/347))
|
|
26
|
+
|
|
27
|
+
## 1.8.0
|
|
6
28
|
|
|
7
29
|
### Added
|
|
8
30
|
|
|
9
|
-
* warn when decryption fails on `run`
|
|
31
|
+
* warn when decryption fails on `run` ([#339](https://github.com/dotenvx/dotenvx/pull/339))
|
|
10
32
|
* decrypt expanded values as necessary ([#336](https://github.com/dotenvx/dotenvx/pull/336))
|
|
11
33
|
|
|
12
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -27,31 +27,31 @@ console.log(`Hello ${process.env.HELLO}`)
|
|
|
27
27
|
|
|
28
28
|
or install globally - *unlocks dotenv for any language, framework, or platform!*
|
|
29
29
|
|
|
30
|
-
<details><summary>with
|
|
30
|
+
<details><summary>with curl 🌐 </summary><br>
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
|
-
|
|
33
|
+
curl -sfS https://dotenvx.sh | sh
|
|
34
34
|
dotenvx help
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
[](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
|
|
38
|
+
[](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
|
|
39
|
+
[](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
|
|
40
|
+
<sup>*curl installs sourced from npm binary packages - <a href="https://www.npmjs.com/package/@dotenvx/dotenvx-linux-x86_64">example</a></sup>
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
</details>
|
|
43
45
|
|
|
44
|
-
<details><summary>with
|
|
46
|
+
<details><summary>with brew 🍺</summary><br>
|
|
45
47
|
|
|
46
48
|
```sh
|
|
47
|
-
|
|
49
|
+
brew install dotenvx/brew/dotenvx
|
|
48
50
|
dotenvx help
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
[](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
|
|
54
|
-
<sup>*curl installs sourced from npm binary packages - <a href="https://www.npmjs.com/package/@dotenvx/dotenvx-linux-x86_64">example</a></sup>
|
|
53
|
+
[](https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb)
|
|
54
|
+
<sup>*homebrew installs sourced from github releases - <a href="https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb">formula</a></sup>
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
@@ -866,7 +866,7 @@ More examples
|
|
|
866
866
|
</details>
|
|
867
867
|
* <details><summary>`run --verbose`</summary><br>
|
|
868
868
|
|
|
869
|
-
Set log level to `verbose`. ([log levels](https://
|
|
869
|
+
Set log level to `verbose`. ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
|
|
870
870
|
|
|
871
871
|
```sh
|
|
872
872
|
$ echo "HELLO=production" > .env.production
|
|
@@ -882,7 +882,7 @@ More examples
|
|
|
882
882
|
</details>
|
|
883
883
|
* <details><summary>`run --debug`</summary><br>
|
|
884
884
|
|
|
885
|
-
Set log level to `debug`. ([log levels](https://
|
|
885
|
+
Set log level to `debug`. ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
|
|
886
886
|
|
|
887
887
|
```sh
|
|
888
888
|
$ echo "HELLO=production" > .env.production
|
|
@@ -904,7 +904,7 @@ More examples
|
|
|
904
904
|
</details>
|
|
905
905
|
* <details><summary>`run --quiet`</summary><br>
|
|
906
906
|
|
|
907
|
-
Use `--quiet` to suppress all output (except errors). ([log levels](https://
|
|
907
|
+
Use `--quiet` to suppress all output (except errors). ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
|
|
908
908
|
|
|
909
909
|
```sh
|
|
910
910
|
$ echo "HELLO=production" > .env.production
|
|
@@ -927,7 +927,7 @@ More examples
|
|
|
927
927
|
Hello production
|
|
928
928
|
```
|
|
929
929
|
|
|
930
|
-
Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://
|
|
930
|
+
Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
|
|
931
931
|
|
|
932
932
|
</details>
|
|
933
933
|
* <details><summary>`run --convention=nextjs`</summary><br>
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.9.1",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"picomatch": "^4.0.2",
|
|
47
47
|
"tinyexec": "^0.2.0",
|
|
48
48
|
"which": "^4.0.0",
|
|
49
|
-
"winston": "^3.11.0",
|
|
50
49
|
"xxhashjs": "^0.2.2"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
@@ -13,7 +13,7 @@ function decrypt () {
|
|
|
13
13
|
if (options.stdout) {
|
|
14
14
|
const {
|
|
15
15
|
processedEnvFiles
|
|
16
|
-
} = main.decrypt(options.envFile, options.key)
|
|
16
|
+
} = main.decrypt(options.envFile, options.key, options.excludeKey)
|
|
17
17
|
|
|
18
18
|
for (const processedEnvFile of processedEnvFiles) {
|
|
19
19
|
process.stdout.write(processedEnvFile.envSrc)
|
|
@@ -25,7 +25,7 @@ function decrypt () {
|
|
|
25
25
|
processedEnvFiles,
|
|
26
26
|
changedFilepaths,
|
|
27
27
|
unchangedFilepaths
|
|
28
|
-
} = main.decrypt(options.envFile, options.key)
|
|
28
|
+
} = main.decrypt(options.envFile, options.key, options.excludeKey)
|
|
29
29
|
|
|
30
30
|
for (const processedEnvFile of processedEnvFiles) {
|
|
31
31
|
logger.verbose(`decrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
|
|
@@ -15,7 +15,7 @@ function encrypt () {
|
|
|
15
15
|
if (options.stdout) {
|
|
16
16
|
const {
|
|
17
17
|
processedEnvFiles
|
|
18
|
-
} = main.encrypt(options.envFile, options.key)
|
|
18
|
+
} = main.encrypt(options.envFile, options.key, options.excludeKey)
|
|
19
19
|
|
|
20
20
|
for (const processedEnvFile of processedEnvFiles) {
|
|
21
21
|
process.stdout.write(processedEnvFile.envSrc)
|
|
@@ -27,7 +27,7 @@ function encrypt () {
|
|
|
27
27
|
processedEnvFiles,
|
|
28
28
|
changedFilepaths,
|
|
29
29
|
unchangedFilepaths
|
|
30
|
-
} = main.encrypt(options.envFile, options.key)
|
|
30
|
+
} = main.encrypt(options.envFile, options.key, options.excludeKey)
|
|
31
31
|
|
|
32
32
|
for (const processedEnvFile of processedEnvFiles) {
|
|
33
33
|
logger.verbose(`encrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
|
|
@@ -20,9 +20,6 @@ function genexample (directory) {
|
|
|
20
20
|
|
|
21
21
|
logger.verbose(`loading env from ${envFile}`)
|
|
22
22
|
|
|
23
|
-
// TODO: display pre-existing
|
|
24
|
-
// TODO: display added/appended/injected
|
|
25
|
-
|
|
26
23
|
fs.writeFileSync(exampleFilepath, envExampleFile, ENCODING)
|
|
27
24
|
|
|
28
25
|
if (addedKeys.length > 0) {
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -100,6 +100,7 @@ program.command('encrypt')
|
|
|
100
100
|
.description('convert .env file(s) to encrypted .env file(s)')
|
|
101
101
|
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
102
102
|
.option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
|
|
103
|
+
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
|
|
103
104
|
.option('--stdout', 'send to stdout')
|
|
104
105
|
.action(encryptAction)
|
|
105
106
|
|
|
@@ -109,6 +110,7 @@ program.command('decrypt')
|
|
|
109
110
|
.description('convert encrypted .env file(s) to plain .env file(s)')
|
|
110
111
|
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
111
112
|
.option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
|
|
113
|
+
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
|
|
112
114
|
.option('--stdout', 'send to stdout')
|
|
113
115
|
.action(decryptAction)
|
|
114
116
|
|
|
@@ -132,40 +134,8 @@ program.command('help [command]')
|
|
|
132
134
|
program.addCommand(require('./commands/ext'))
|
|
133
135
|
|
|
134
136
|
//
|
|
135
|
-
//
|
|
137
|
+
// MOVED
|
|
136
138
|
//
|
|
137
|
-
const lsAction = require('./actions/ext/ls')
|
|
138
|
-
program.command('ls')
|
|
139
|
-
.description('DEPRECATED: moved to [dotenvx ext ls]')
|
|
140
|
-
.argument('[directory]', 'directory to list .env files from', '.')
|
|
141
|
-
.option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
|
|
142
|
-
.action(function (...args) {
|
|
143
|
-
logger.warn('DEPRECATION NOTICE: [ls] has moved to [dotenvx ext ls]')
|
|
144
|
-
|
|
145
|
-
lsAction.apply(this, args)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
const genexampleAction = require('./actions/ext/genexample')
|
|
149
|
-
program.command('genexample')
|
|
150
|
-
.description('DEPRECATED: moved to [dotenvx ext genexample]')
|
|
151
|
-
.argument('[directory]', 'directory to generate from', '.')
|
|
152
|
-
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
|
|
153
|
-
.action(function (...args) {
|
|
154
|
-
logger.warn('DEPRECATION NOTICE: [genexample] has moved to [dotenvx ext genexample]')
|
|
155
|
-
|
|
156
|
-
genexampleAction.apply(this, args)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const gitignoreAction = require('./actions/ext/gitignore')
|
|
160
|
-
program.command('gitignore')
|
|
161
|
-
.description('DEPRECATED: moved to [dotenvx ext gitignore]')
|
|
162
|
-
.addHelpText('after', examples.gitignore)
|
|
163
|
-
.action(function (...args) {
|
|
164
|
-
logger.warn('DEPRECATION NOTICE: [gitignore] has moved to [dotenvx ext gitignore]')
|
|
165
|
-
|
|
166
|
-
gitignoreAction.apply(this, args)
|
|
167
|
-
})
|
|
168
|
-
|
|
169
139
|
const prebuildAction = require('./actions/ext/prebuild')
|
|
170
140
|
program.command('prebuild')
|
|
171
141
|
.description('DEPRECATED: moved to [dotenvx ext prebuild]')
|
|
@@ -187,15 +157,6 @@ program.command('precommit')
|
|
|
187
157
|
precommitAction.apply(this, args)
|
|
188
158
|
})
|
|
189
159
|
|
|
190
|
-
const scanAction = require('./actions/ext/scan')
|
|
191
|
-
program.command('scan')
|
|
192
|
-
.description('DEPRECATED: moved to [dotenvx ext scan]')
|
|
193
|
-
.action(function (...args) {
|
|
194
|
-
logger.warn('DEPRECATION NOTICE: [scan] has moved to [dotenvx ext scan]')
|
|
195
|
-
|
|
196
|
-
scanAction.apply(this, args)
|
|
197
|
-
})
|
|
198
|
-
|
|
199
160
|
// overide helpInformation to hide DEPRECATED commands
|
|
200
161
|
program.helpInformation = function () {
|
|
201
162
|
const originalHelp = Command.prototype.helpInformation.call(this)
|
|
@@ -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
|
|
|
@@ -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.js
CHANGED
|
@@ -189,13 +189,13 @@ const set = function (key, value, envFile, encrypt) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/** @type {import('./main').encrypt} */
|
|
192
|
-
const encrypt = function (envFile, key) {
|
|
193
|
-
return new Encrypt(envFile, key).run()
|
|
192
|
+
const encrypt = function (envFile, key, excludeKey) {
|
|
193
|
+
return new Encrypt(envFile, key, excludeKey).run()
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
/** @type {import('./main').encrypt} */
|
|
197
|
-
const decrypt = function (envFile, key) {
|
|
198
|
-
return new Decrypt(envFile, key).run()
|
|
197
|
+
const decrypt = function (envFile, key, excludeKey) {
|
|
198
|
+
return new Decrypt(envFile, key, excludeKey).run()
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/** @type {import('./main').status} */
|
|
@@ -14,10 +14,12 @@ class Decrypt {
|
|
|
14
14
|
/**
|
|
15
15
|
* @param {string|string[]} [envFile]
|
|
16
16
|
* @param {string|string[]} [key]
|
|
17
|
+
* @param {string|string[]} [excludeKey]
|
|
17
18
|
**/
|
|
18
|
-
constructor (envFile = '.env', key = []) {
|
|
19
|
+
constructor (envFile = '.env', key = [], excludeKey = []) {
|
|
19
20
|
this.envFile = envFile
|
|
20
21
|
this.key = key
|
|
22
|
+
this.excludeKey = excludeKey
|
|
21
23
|
this.processedEnvFiles = []
|
|
22
24
|
this.changedFilepaths = new Set()
|
|
23
25
|
this.unchangedFilepaths = new Set()
|
|
@@ -26,6 +28,7 @@ class Decrypt {
|
|
|
26
28
|
run () {
|
|
27
29
|
const envFilepaths = this._envFilepaths()
|
|
28
30
|
const keys = this._keys()
|
|
31
|
+
const excludeKeys = this._excludeKeys()
|
|
29
32
|
for (const envFilepath of envFilepaths) {
|
|
30
33
|
const filepath = path.resolve(envFilepath)
|
|
31
34
|
|
|
@@ -49,17 +52,25 @@ class Decrypt {
|
|
|
49
52
|
// iterate over all non-encrypted values and encrypt them
|
|
50
53
|
const parsed = dotenv.parse(src)
|
|
51
54
|
for (const [key, value] of Object.entries(parsed)) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
// key excluded - don't decrypt it
|
|
56
|
+
if (excludeKeys.includes(key)) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// key effectively excluded (by not being in the list of includes) - don't encrypt it
|
|
61
|
+
if (keys.length > 0 && !keys.includes(key)) {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const encrypted = isEncrypted(key, value)
|
|
66
|
+
if (encrypted) {
|
|
67
|
+
row.keys.push(key) // track key(s)
|
|
56
68
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
const decryptedValue = decryptValue(value, privateKey)
|
|
70
|
+
// once newSrc is built write it out
|
|
71
|
+
src = replace(src, key, decryptedValue)
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
}
|
|
73
|
+
row.changed = true // track change
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
|
|
@@ -106,6 +117,14 @@ class Decrypt {
|
|
|
106
117
|
|
|
107
118
|
return this.key
|
|
108
119
|
}
|
|
120
|
+
|
|
121
|
+
_excludeKeys () {
|
|
122
|
+
if (!Array.isArray(this.excludeKey)) {
|
|
123
|
+
return [this.excludeKey]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.excludeKey
|
|
127
|
+
}
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
module.exports = Decrypt
|
|
@@ -15,10 +15,12 @@ class Encrypt {
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {string|string[]} [envFile]
|
|
17
17
|
* @param {string|string[]} [key]
|
|
18
|
+
* @param {string|string[]} [excludeKey]
|
|
18
19
|
**/
|
|
19
|
-
constructor (envFile = '.env', key = []) {
|
|
20
|
+
constructor (envFile = '.env', key = [], excludeKey = []) {
|
|
20
21
|
this.envFile = envFile
|
|
21
22
|
this.key = key
|
|
23
|
+
this.excludeKey = excludeKey
|
|
22
24
|
this.processedEnvFiles = []
|
|
23
25
|
this.changedFilepaths = new Set()
|
|
24
26
|
this.unchangedFilepaths = new Set()
|
|
@@ -27,6 +29,7 @@ class Encrypt {
|
|
|
27
29
|
run () {
|
|
28
30
|
const envFilepaths = this._envFilepaths()
|
|
29
31
|
const keys = this._keys()
|
|
32
|
+
const excludeKeys = this._excludeKeys()
|
|
30
33
|
for (const envFilepath of envFilepaths) {
|
|
31
34
|
const filepath = path.resolve(envFilepath)
|
|
32
35
|
|
|
@@ -63,19 +66,25 @@ class Encrypt {
|
|
|
63
66
|
// iterate over all non-encrypted values and encrypt them
|
|
64
67
|
const parsed = dotenv.parse(src)
|
|
65
68
|
for (const [key, value] of Object.entries(parsed)) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
// key excluded - don't encrypt it
|
|
70
|
+
if (excludeKeys.includes(key)) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// key effectively excluded (by not being in the list of includes) - don't encrypt it
|
|
75
|
+
if (keys.length > 0 && !keys.includes(key)) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const encrypted = isEncrypted(key, value) || isPublicKey(key, value)
|
|
80
|
+
if (!encrypted) {
|
|
81
|
+
row.keys.push(key) // track key(s)
|
|
82
|
+
|
|
83
|
+
const encryptedValue = encryptValue(value, publicKey)
|
|
84
|
+
// once newSrc is built write it out
|
|
85
|
+
src = replace(src, key, encryptedValue)
|
|
86
|
+
|
|
87
|
+
row.changed = true // track change
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -122,6 +131,14 @@ class Encrypt {
|
|
|
122
131
|
|
|
123
132
|
return this.key
|
|
124
133
|
}
|
|
134
|
+
|
|
135
|
+
_excludeKeys () {
|
|
136
|
+
if (!Array.isArray(this.excludeKey)) {
|
|
137
|
+
return [this.excludeKey]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return this.excludeKey
|
|
141
|
+
}
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
module.exports = Encrypt
|
|
@@ -5,11 +5,15 @@ const dotenv = require('dotenv')
|
|
|
5
5
|
const ENCODING = 'utf8'
|
|
6
6
|
|
|
7
7
|
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
8
|
+
const replace = require('../helpers/replace')
|
|
8
9
|
|
|
9
10
|
class Genexample {
|
|
10
11
|
constructor (directory = '.', envFile) {
|
|
11
12
|
this.directory = directory
|
|
12
13
|
this.envFile = envFile || findEnvFiles(directory)
|
|
14
|
+
|
|
15
|
+
this.exampleFilename = '.env.example'
|
|
16
|
+
this.exampleFilepath = path.resolve(this.directory, this.exampleFilename)
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
run () {
|
|
@@ -27,6 +31,12 @@ class Genexample {
|
|
|
27
31
|
const keys = new Set()
|
|
28
32
|
const addedKeys = new Set()
|
|
29
33
|
const envFilepaths = this._envFilepaths()
|
|
34
|
+
/** @type {Record<string, string>} */
|
|
35
|
+
const injected = {}
|
|
36
|
+
/** @type {Record<string, string>} */
|
|
37
|
+
const preExisted = {}
|
|
38
|
+
|
|
39
|
+
let exampleSrc = `# ${this.exampleFilename} - generated with dotenvx\n`
|
|
30
40
|
|
|
31
41
|
for (const envFilepath of envFilepaths) {
|
|
32
42
|
const filepath = path.resolve(this.directory, envFilepath)
|
|
@@ -41,43 +51,51 @@ class Genexample {
|
|
|
41
51
|
throw error
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
// get the original src
|
|
55
|
+
let src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
56
|
+
const parsed = dotenv.parse(src)
|
|
57
|
+
for (const key in parsed) {
|
|
58
|
+
// used later
|
|
46
59
|
keys.add(key)
|
|
60
|
+
|
|
61
|
+
// once newSrc is built write it out
|
|
62
|
+
src = replace(src, key, '') // empty value
|
|
47
63
|
}
|
|
48
|
-
}
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
const exampleFilename = '.env.example'
|
|
52
|
-
const exampleFilepath = path.resolve(this.directory, exampleFilename)
|
|
53
|
-
if (!fs.existsSync(exampleFilepath)) {
|
|
54
|
-
envExampleFile += `# ${exampleFilename} - generated with dotenvx\n`
|
|
55
|
-
} else {
|
|
56
|
-
envExampleFile = fs.readFileSync(exampleFilepath, ENCODING)
|
|
65
|
+
exampleSrc += `\n${src}`
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (const key of [...keys]) {
|
|
66
|
-
if (key in currentEnvExample) {
|
|
67
|
-
preExisted[key] = currentEnvExample[key]
|
|
68
|
-
} else {
|
|
69
|
-
envExampleFile += `${key}=""\n`
|
|
70
|
-
|
|
68
|
+
if (!fs.existsSync(this.exampleFilepath)) {
|
|
69
|
+
// it doesn't exist so just write this first generated one
|
|
70
|
+
// exampleSrc - already written to from the prior loop
|
|
71
|
+
for (const key of [...keys]) {
|
|
72
|
+
// every key is added since it's the first time generating .env.example
|
|
71
73
|
addedKeys.add(key)
|
|
72
74
|
|
|
73
75
|
injected[key] = ''
|
|
74
76
|
}
|
|
77
|
+
} else {
|
|
78
|
+
// it already exists (which means the user might have it modified a way in which they prefer, so replace exampleSrc with their existing .env.example)
|
|
79
|
+
exampleSrc = fs.readFileSync(this.exampleFilepath, ENCODING)
|
|
80
|
+
|
|
81
|
+
const parsed = dotenv.parse(exampleSrc)
|
|
82
|
+
for (const key of [...keys]) {
|
|
83
|
+
if (key in parsed) {
|
|
84
|
+
preExisted[key] = parsed[key]
|
|
85
|
+
} else {
|
|
86
|
+
exampleSrc += `${key}=""\n`
|
|
87
|
+
|
|
88
|
+
addedKeys.add(key)
|
|
89
|
+
|
|
90
|
+
injected[key] = ''
|
|
91
|
+
}
|
|
92
|
+
}
|
|
75
93
|
}
|
|
76
94
|
|
|
77
95
|
return {
|
|
78
|
-
envExampleFile,
|
|
96
|
+
envExampleFile: exampleSrc,
|
|
79
97
|
envFile: this.envFile,
|
|
80
|
-
exampleFilepath,
|
|
98
|
+
exampleFilepath: this.exampleFilepath,
|
|
81
99
|
addedKeys: [...addedKeys],
|
|
82
100
|
injected,
|
|
83
101
|
preExisted
|
package/src/shared/logger.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const winston = require('winston')
|
|
2
|
-
|
|
3
|
-
const printf = winston.format.printf
|
|
4
|
-
const combine = winston.format.combine
|
|
5
|
-
const createLogger = winston.createLogger
|
|
6
|
-
const transports = winston.transports
|
|
7
|
-
|
|
8
1
|
const packageJson = require('../lib/helpers/packageJson')
|
|
9
2
|
const { getColor, bold } = require('./colors')
|
|
10
3
|
|
|
@@ -26,7 +19,6 @@ const levels = {
|
|
|
26
19
|
help: 2,
|
|
27
20
|
help2: 2,
|
|
28
21
|
blank: 2,
|
|
29
|
-
http: 3,
|
|
30
22
|
verbose: 4,
|
|
31
23
|
debug: 5,
|
|
32
24
|
silly: 6
|
|
@@ -38,14 +30,27 @@ const success = getColor('green')
|
|
|
38
30
|
const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
|
|
39
31
|
const help = getColor('blue')
|
|
40
32
|
const help2 = getColor('gray')
|
|
41
|
-
const http = getColor('green')
|
|
42
33
|
const verbose = getColor('plum')
|
|
43
34
|
const debug = getColor('plum')
|
|
44
35
|
|
|
45
|
-
|
|
36
|
+
let currentLevel = levels.info // default log level
|
|
37
|
+
|
|
38
|
+
function log (level, message) {
|
|
39
|
+
if (levels[level] === undefined) {
|
|
40
|
+
throw new Error(`MISSING_LOG_LEVEL: '${level}'. implement in logger.`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (levels[level] <= currentLevel) {
|
|
44
|
+
const formattedMessage = formatMessage(level, message)
|
|
45
|
+
console.log(formattedMessage)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatMessage (level, message) {
|
|
46
50
|
const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
|
|
47
51
|
|
|
48
52
|
switch (level.toLowerCase()) {
|
|
53
|
+
// errors
|
|
49
54
|
case 'error':
|
|
50
55
|
return error(formattedMessage)
|
|
51
56
|
case 'errorv':
|
|
@@ -56,6 +61,7 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
|
|
|
56
61
|
return error(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
|
|
57
62
|
case 'errornocolor':
|
|
58
63
|
return formattedMessage
|
|
64
|
+
// warns
|
|
59
65
|
case 'warn':
|
|
60
66
|
return warn(formattedMessage)
|
|
61
67
|
case 'warnv':
|
|
@@ -64,6 +70,7 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
|
|
|
64
70
|
return warn(`[dotenvx@${packageJson.version}][precommit] ${formattedMessage}`)
|
|
65
71
|
case 'warnvpb':
|
|
66
72
|
return warn(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
|
|
73
|
+
// successes
|
|
67
74
|
case 'success':
|
|
68
75
|
return success(formattedMessage)
|
|
69
76
|
case 'successv': // success with 'version'
|
|
@@ -72,35 +79,66 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
|
|
|
72
79
|
return success(`[dotenvx@${packageJson.version}][precommit] ${formattedMessage}`)
|
|
73
80
|
case 'successvpb': // success with 'version' and precommit
|
|
74
81
|
return success(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
|
|
82
|
+
// info
|
|
75
83
|
case 'info':
|
|
76
84
|
return formattedMessage
|
|
85
|
+
// help
|
|
77
86
|
case 'help':
|
|
78
87
|
return help(formattedMessage)
|
|
79
88
|
case 'help2':
|
|
80
89
|
return help2(formattedMessage)
|
|
81
|
-
|
|
82
|
-
return http(formattedMessage)
|
|
90
|
+
// verbose
|
|
83
91
|
case 'verbose':
|
|
84
92
|
return verbose(formattedMessage)
|
|
93
|
+
// debug
|
|
85
94
|
case 'debug':
|
|
86
95
|
return debug(formattedMessage)
|
|
96
|
+
// blank
|
|
87
97
|
case 'blank': // custom
|
|
88
98
|
return formattedMessage
|
|
89
99
|
}
|
|
90
|
-
}
|
|
100
|
+
}
|
|
91
101
|
|
|
92
|
-
const logger =
|
|
102
|
+
const logger = {
|
|
103
|
+
// track level
|
|
93
104
|
level: 'info',
|
|
94
|
-
levels,
|
|
95
|
-
format: combine(
|
|
96
|
-
dotenvxFormat
|
|
97
|
-
),
|
|
98
|
-
transports: [
|
|
99
|
-
new transports.Console()
|
|
100
|
-
]
|
|
101
|
-
})
|
|
102
105
|
|
|
103
|
-
|
|
106
|
+
// errors
|
|
107
|
+
error: (msg) => log('error', msg),
|
|
108
|
+
errorv: (msg) => log('errorv', msg),
|
|
109
|
+
errorvp: (msg) => log('errorvp', msg),
|
|
110
|
+
errorvpb: (msg) => log('errorvpb', msg),
|
|
111
|
+
errornocolor: (msg) => log('errornocolor', msg),
|
|
112
|
+
// warns
|
|
113
|
+
warn: (msg) => log('warn', msg),
|
|
114
|
+
warnv: (msg) => log('warnv', msg),
|
|
115
|
+
warnvp: (msg) => log('warnvp', msg),
|
|
116
|
+
warnvpb: (msg) => log('warnvpb', msg),
|
|
117
|
+
// success
|
|
118
|
+
success: (msg) => log('success', msg),
|
|
119
|
+
successv: (msg) => log('successv', msg),
|
|
120
|
+
successvp: (msg) => log('successvp', msg),
|
|
121
|
+
successvpb: (msg) => log('successvpb', msg),
|
|
122
|
+
// info
|
|
123
|
+
info: (msg) => log('info', msg),
|
|
124
|
+
// help
|
|
125
|
+
help: (msg) => log('help', msg),
|
|
126
|
+
help2: (msg) => log('help2', msg),
|
|
127
|
+
// verbose
|
|
128
|
+
verbose: (msg) => log('verbose', msg),
|
|
129
|
+
// debug
|
|
130
|
+
debug: (msg) => log('debug', msg),
|
|
131
|
+
// blank
|
|
132
|
+
blank: (msg) => log('blank', msg),
|
|
133
|
+
setLevel: (level) => {
|
|
134
|
+
if (levels[level] !== undefined) {
|
|
135
|
+
currentLevel = levels[level]
|
|
136
|
+
logger.level = level
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function setLogLevel (options) {
|
|
104
142
|
const logLevel = options.debug
|
|
105
143
|
? 'debug'
|
|
106
144
|
: options.verbose
|
|
@@ -110,7 +148,7 @@ const setLogLevel = options => {
|
|
|
110
148
|
: options.logLevel
|
|
111
149
|
|
|
112
150
|
if (!logLevel) return
|
|
113
|
-
logger.
|
|
151
|
+
logger.setLevel(logLevel)
|
|
114
152
|
// Only log which level it's setting if it's not set to quiet mode
|
|
115
153
|
if (!options.quiet || (options.quiet && logLevel !== 'error')) {
|
|
116
154
|
logger.debug(`Setting log level to ${logLevel}`)
|
|
@@ -120,5 +158,6 @@ const setLogLevel = options => {
|
|
|
120
158
|
module.exports = {
|
|
121
159
|
logger,
|
|
122
160
|
getColor,
|
|
123
|
-
setLogLevel
|
|
161
|
+
setLogLevel,
|
|
162
|
+
levels
|
|
124
163
|
}
|