@dotenvx/dotenvx 0.39.0 → 0.41.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/README.md +86 -0
- package/package.json +1 -1
- package/src/cli/actions/convert.js +73 -0
- package/src/cli/actions/run.js +5 -2
- package/src/cli/actions/set.js +43 -9
- package/src/cli/actions/vault/convert.js +47 -0
- package/src/cli/actions/vault/encrypt.js +0 -1
- package/src/cli/commands/hub.js +7 -7
- package/src/cli/commands/vault.js +5 -0
- package/src/cli/dotenvx.js +8 -1
- package/src/lib/helpers/findOrCreatePublicKey.js +4 -2
- package/src/lib/helpers/isEncrypted.js +8 -0
- package/src/lib/helpers/isFullyEncrypted.js +18 -0
- package/src/lib/helpers/isIgnoringDotenvKeys.js +19 -0
- package/src/lib/helpers/replace.js +1 -1
- package/src/lib/main.js +14 -3
- package/src/lib/services/encrypt.js +73 -87
- package/src/lib/services/precommit.js +19 -8
- package/src/lib/services/sets.js +32 -10
- package/src/lib/services/vaultEncrypt.js +118 -0
package/README.md
CHANGED
|
@@ -624,6 +624,16 @@ More examples
|
|
|
624
624
|
DATABASE_URL postgres://yourusername@localhost/my_database
|
|
625
625
|
```
|
|
626
626
|
|
|
627
|
+
</details>
|
|
628
|
+
* <details><summary>`run` - Shell Expansion</summary><br>
|
|
629
|
+
|
|
630
|
+
Prevent your shell from expanding inline `$VARIABLES` before dotenvx has a chance to inject it. Use a subshell.
|
|
631
|
+
|
|
632
|
+
```sh
|
|
633
|
+
$ dotenvx run --env="HELLO=World" -- sh -c 'echo Hello $HELLO'
|
|
634
|
+
Hello World
|
|
635
|
+
```
|
|
636
|
+
|
|
627
637
|
</details>
|
|
628
638
|
* <details><summary>`run` - multiple `-f` flags</summary><br>
|
|
629
639
|
|
|
@@ -668,6 +678,82 @@ More examples
|
|
|
668
678
|
Hello World
|
|
669
679
|
```
|
|
670
680
|
|
|
681
|
+
</details>
|
|
682
|
+
* <details><summary>`DOTENV_PRIVATE_KEY=key run`</summary><br>
|
|
683
|
+
|
|
684
|
+
Decrypt your encrypted `.env` by setting `DOTENV_PRIVATE_KEY` before `dotenvx run`.
|
|
685
|
+
|
|
686
|
+
```sh
|
|
687
|
+
$ touch .env
|
|
688
|
+
$ dotenvx set HELLO encrypted --encrypt
|
|
689
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
690
|
+
|
|
691
|
+
# check your .env.keys files for your privateKey
|
|
692
|
+
$ DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
|
|
693
|
+
[dotenvx] injecting env (2) from .env
|
|
694
|
+
Hello encrypted
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
</details>
|
|
698
|
+
* <details><summary>`DOTENV_PRIVATE_KEY_PRODUCTION=key run`</summary><br>
|
|
699
|
+
|
|
700
|
+
Decrypt your encrypted `.env.production` by setting `DOTENV_PRIVATE_KEY_PRODUCTION` before `dotenvx run`. Alternatively, this can be already set on your server or cloud provider.
|
|
701
|
+
|
|
702
|
+
```sh
|
|
703
|
+
$ touch .env.production
|
|
704
|
+
$ dotenvx set HELLO "production encrypted" -f .env.production --encrypt
|
|
705
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
706
|
+
|
|
707
|
+
# check .env.keys for your privateKey
|
|
708
|
+
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
|
|
709
|
+
[dotenvx] injecting env (2) from .env.production
|
|
710
|
+
Hello production encrypted
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Note the `DOTENV_PRIVATE_KEY_PRODUCTION` ends with `_PRODUCTION`. This instructs dotenvx run to load the `.env.production` file.
|
|
714
|
+
|
|
715
|
+
</details>
|
|
716
|
+
* <details><summary>`DOTENV_PRIVATE_KEY_CI=key dotenvx run`</summary><br>
|
|
717
|
+
|
|
718
|
+
Decrypt your encrypted `.env.ci` by setting `DOTENV_PRIVATE_KEY_CI` before `dotenvx run`. Alternatively, this can be already set on your server or cloud provider.
|
|
719
|
+
|
|
720
|
+
```sh
|
|
721
|
+
$ touch .env.ci
|
|
722
|
+
$ dotenvx set HELLO "ci encrypted" -f .env.production --encrypt
|
|
723
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
724
|
+
|
|
725
|
+
# check .env.keys for your privateKey
|
|
726
|
+
$ DOTENV_PRIVATE_KEY_CI="122...0b8" dotenvx run -- node index.js
|
|
727
|
+
[dotenvx] injecting env (2) from .env.ci
|
|
728
|
+
Hello ci encrypted
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
Note the `DOTENV_PRIVATE_KEY_CI` ends with `_CI`. This instructs dotenvx run to load the `.env.ci` file. See the pattern?
|
|
732
|
+
|
|
733
|
+
</details>
|
|
734
|
+
* <details><summary>`DOTENV_PRIVATE_KEY=key DOTENV_PRIVATE_KEY_PRODUCTION=key run` - Combine Multiple</summary><br>
|
|
735
|
+
|
|
736
|
+
Decrypt your encrypted `.env` and `.env.production` files by setting `DOTENV_PRIVATE_KEY` and `DOTENV_PRIVATE_KEY_PRODUCTION` before `dotenvx run`.
|
|
737
|
+
|
|
738
|
+
```sh
|
|
739
|
+
$ touch .env
|
|
740
|
+
$ touch .env.production
|
|
741
|
+
$ dotenvx set HELLO encrypted --encrypt
|
|
742
|
+
$ dotenvx set HELLO "production encrypted" -f .env.production --encrypt
|
|
743
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
744
|
+
|
|
745
|
+
# check .env.keys for your privateKeys
|
|
746
|
+
$ DOTENV_PRIVATE_KEY="122...0b8" DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
|
|
747
|
+
[dotenvx] injecting env (3) from .env, .env.production
|
|
748
|
+
Hello encrypted
|
|
749
|
+
|
|
750
|
+
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
|
|
751
|
+
[dotenvx] injecting env (3) from .env.production, .env
|
|
752
|
+
Hello production encrypted
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Compose any encrypted files you want this way. As long as a `DOTENV_PRIVATE_KEY_${environment}` is set, the values from `.env.${environment}` will be decrypted at runtime.
|
|
756
|
+
|
|
671
757
|
</details>
|
|
672
758
|
* <details><summary>`run --verbose`</summary><br>
|
|
673
759
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const logger = require('./../../shared/logger')
|
|
3
|
+
|
|
4
|
+
const main = require('./../../lib/main')
|
|
5
|
+
|
|
6
|
+
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys')
|
|
7
|
+
|
|
8
|
+
const ENCODING = 'utf8'
|
|
9
|
+
|
|
10
|
+
async function convert () {
|
|
11
|
+
const options = this.opts()
|
|
12
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const {
|
|
16
|
+
processedEnvFiles,
|
|
17
|
+
changedFilepaths,
|
|
18
|
+
unchangedFilepaths
|
|
19
|
+
} = main.convert(options.envFile)
|
|
20
|
+
|
|
21
|
+
for (const processedEnvFile of processedEnvFiles) {
|
|
22
|
+
logger.verbose(`encrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
|
|
23
|
+
if (processedEnvFile.error) {
|
|
24
|
+
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') {
|
|
25
|
+
logger.warn(processedEnvFile.error)
|
|
26
|
+
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx convert]`)
|
|
27
|
+
} else {
|
|
28
|
+
logger.warn(processedEnvFile.error)
|
|
29
|
+
}
|
|
30
|
+
} else if (processedEnvFile.changed) {
|
|
31
|
+
fs.writeFileSync(processedEnvFile.filepath, processedEnvFile.envSrc, ENCODING)
|
|
32
|
+
|
|
33
|
+
logger.verbose(`encrypted ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
|
|
34
|
+
} else {
|
|
35
|
+
logger.verbose(`no changes ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (changedFilepaths.length > 0) {
|
|
40
|
+
logger.success(`✔ encrypted (${changedFilepaths.join(',')})`)
|
|
41
|
+
} else if (unchangedFilepaths.length > 0) {
|
|
42
|
+
logger.info(`no changes (${unchangedFilepaths})`)
|
|
43
|
+
} else {
|
|
44
|
+
// do nothing - scenario when no .env files found
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const processedEnvFile of processedEnvFiles) {
|
|
48
|
+
if (processedEnvFile.privateKeyAdded) {
|
|
49
|
+
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`)
|
|
50
|
+
|
|
51
|
+
if (!isIgnoringDotenvKeys()) {
|
|
52
|
+
logger.help2('ℹ add .env.keys to .gitignore: [echo ".env.keys" >> .gitignore]')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx run -- yourcommand] to test decryption locally`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error(error.message)
|
|
60
|
+
if (error.help) {
|
|
61
|
+
logger.help(error.help)
|
|
62
|
+
}
|
|
63
|
+
if (error.debug) {
|
|
64
|
+
logger.debug(error.debug)
|
|
65
|
+
}
|
|
66
|
+
if (error.code) {
|
|
67
|
+
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
68
|
+
}
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = convert
|
package/src/cli/actions/run.js
CHANGED
|
@@ -136,8 +136,11 @@ async function run () {
|
|
|
136
136
|
|
|
137
137
|
if (processedEnv.error) {
|
|
138
138
|
if (processedEnv.error.code === 'MISSING_ENV_FILE') {
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// do not warn for conventions (too noisy)
|
|
140
|
+
if (!options.convention) {
|
|
141
|
+
logger.warnv(processedEnv.error)
|
|
142
|
+
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.filepath}] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
|
|
143
|
+
}
|
|
141
144
|
} else {
|
|
142
145
|
logger.warnv(processedEnv.error)
|
|
143
146
|
}
|
package/src/cli/actions/set.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
1
2
|
const logger = require('./../../shared/logger')
|
|
2
3
|
|
|
3
4
|
const main = require('./../../lib/main')
|
|
4
5
|
|
|
6
|
+
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys')
|
|
7
|
+
|
|
8
|
+
const ENCODING = 'utf8'
|
|
9
|
+
|
|
5
10
|
function set (key, value) {
|
|
6
11
|
logger.debug(`key: ${key}`)
|
|
7
12
|
logger.debug(`value: ${value}`)
|
|
@@ -12,36 +17,65 @@ function set (key, value) {
|
|
|
12
17
|
try {
|
|
13
18
|
const {
|
|
14
19
|
processedEnvFiles,
|
|
15
|
-
|
|
20
|
+
changedFilepaths,
|
|
21
|
+
unchangedFilepaths
|
|
16
22
|
} = main.set(key, value, options.envFile, options.encrypt)
|
|
17
23
|
|
|
18
|
-
let
|
|
24
|
+
let withEncryption = ''
|
|
25
|
+
|
|
26
|
+
if (options.encrypt) {
|
|
27
|
+
withEncryption = ' with encryption'
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
for (const processedEnvFile of processedEnvFiles) {
|
|
21
|
-
logger.verbose(`setting for ${processedEnvFile.
|
|
31
|
+
logger.verbose(`setting for ${processedEnvFile.envFilepath}`)
|
|
22
32
|
|
|
23
33
|
if (processedEnvFile.error) {
|
|
24
34
|
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') {
|
|
25
35
|
logger.warn(processedEnvFile.error)
|
|
26
|
-
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.
|
|
36
|
+
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx set]`)
|
|
27
37
|
} else {
|
|
28
38
|
logger.warn(processedEnvFile.error)
|
|
29
39
|
}
|
|
30
40
|
} else {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
logger.
|
|
41
|
+
fs.writeFileSync(processedEnvFile.filepath, processedEnvFile.envSrc, ENCODING)
|
|
42
|
+
|
|
43
|
+
logger.verbose(`${processedEnvFile.key} set${withEncryption} (${processedEnvFile.envFilepath})`)
|
|
44
|
+
logger.debug(`${processedEnvFile.key} set${withEncryption} to ${processedEnvFile.value} (${processedEnvFile.envFilepath})`)
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
|
|
37
|
-
if (
|
|
38
|
-
logger.success(
|
|
48
|
+
if (changedFilepaths.length > 0) {
|
|
49
|
+
logger.success(`✔ set ${key}${withEncryption} (${changedFilepaths.join(',')})`)
|
|
50
|
+
} else if (unchangedFilepaths.length > 0) {
|
|
51
|
+
logger.info(`no changes (${unchangedFilepaths})`)
|
|
52
|
+
} else {
|
|
53
|
+
// do nothing
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const processedEnvFile of processedEnvFiles) {
|
|
57
|
+
if (processedEnvFile.privateKeyAdded) {
|
|
58
|
+
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`)
|
|
59
|
+
|
|
60
|
+
if (!isIgnoringDotenvKeys()) {
|
|
61
|
+
logger.help2('ℹ add .env.keys to .gitignore: [echo ".env.keys" >> .gitignore]')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx get ${key}] to test decryption locally`)
|
|
65
|
+
}
|
|
39
66
|
}
|
|
40
67
|
} catch (error) {
|
|
41
68
|
logger.error(error.message)
|
|
42
69
|
if (error.help) {
|
|
43
70
|
logger.help(error.help)
|
|
44
71
|
}
|
|
72
|
+
if (error.debug) {
|
|
73
|
+
logger.debug(error.debug)
|
|
74
|
+
}
|
|
75
|
+
if (error.code) {
|
|
76
|
+
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
77
|
+
}
|
|
78
|
+
process.exit(1)
|
|
45
79
|
}
|
|
46
80
|
}
|
|
47
81
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const logger = require('./../../../shared/logger')
|
|
2
|
+
|
|
3
|
+
function convert (directory) {
|
|
4
|
+
// debug args
|
|
5
|
+
logger.debug(`directory: ${directory}`)
|
|
6
|
+
|
|
7
|
+
const options = this.opts()
|
|
8
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
logger.help2('To convert your .env.vault file to encrypted .env file(s):')
|
|
12
|
+
logger.help('')
|
|
13
|
+
logger.help(' 1. Run [dotenvx vault decrypt]')
|
|
14
|
+
logger.help(' 2. Run [ls -a .env*]')
|
|
15
|
+
logger.help('')
|
|
16
|
+
logger.help2('Lastly, convert each .env(.environment) file:')
|
|
17
|
+
logger.help('')
|
|
18
|
+
logger.help(' 3. Run [dotenvx convert -f .env.production]')
|
|
19
|
+
logger.help2('')
|
|
20
|
+
logger.help2('For example:')
|
|
21
|
+
logger.help2('')
|
|
22
|
+
logger.help2(' $ dotenvx convert -f .env')
|
|
23
|
+
logger.help2(' $ dotenvx convert -f .env.ci')
|
|
24
|
+
logger.help2(' $ dotenvx convert -f .env.production')
|
|
25
|
+
logger.help2('')
|
|
26
|
+
logger.help2('Afterward:')
|
|
27
|
+
logger.help2('')
|
|
28
|
+
logger.help2('Update production with your new DOTENV_PRIVATE_KEY_PRODUCTION located in .env.keys')
|
|
29
|
+
logger.help2('')
|
|
30
|
+
logger.success('Learn more at [https://dotenvx.com/docs/quickstart#add-encryption]')
|
|
31
|
+
logger.help2('')
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.error(error.message)
|
|
34
|
+
if (error.help) {
|
|
35
|
+
logger.help(error.help)
|
|
36
|
+
}
|
|
37
|
+
if (error.debug) {
|
|
38
|
+
logger.debug(error.debug)
|
|
39
|
+
}
|
|
40
|
+
if (error.code) {
|
|
41
|
+
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
42
|
+
}
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = convert
|
|
@@ -58,7 +58,6 @@ async function encrypt (directory) {
|
|
|
58
58
|
|
|
59
59
|
if (addedKeys.length > 0) {
|
|
60
60
|
spinner.succeed(`${pluralize('key', addedKeys.length)} added to .env.keys (${addedKeys})`)
|
|
61
|
-
logger.help2('ℹ push .env.keys up to hub: [dotenvx hub push]')
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
if (addedVaults.length > 0) {
|
package/src/cli/commands/hub.js
CHANGED
|
@@ -14,7 +14,7 @@ hub
|
|
|
14
14
|
.description('authenticate to dotenvx hub')
|
|
15
15
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
16
16
|
.action(function (...args) {
|
|
17
|
-
logger.warn('DEPRECATION
|
|
17
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub login] will be removed in 1.0.0 release soon')
|
|
18
18
|
|
|
19
19
|
loginAction.apply(this, args)
|
|
20
20
|
})
|
|
@@ -26,7 +26,7 @@ hub
|
|
|
26
26
|
.argument('[directory]', 'directory to push', '.')
|
|
27
27
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
28
28
|
.action(function (...args) {
|
|
29
|
-
logger.warn('DEPRECATION
|
|
29
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub push] will be removed in 1.0.0 release soon')
|
|
30
30
|
|
|
31
31
|
pushAction.apply(this, args)
|
|
32
32
|
})
|
|
@@ -38,7 +38,7 @@ hub
|
|
|
38
38
|
.argument('[directory]', 'directory to pull', '.')
|
|
39
39
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
40
40
|
.action(function (...args) {
|
|
41
|
-
logger.warn('DEPRECATION
|
|
41
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub pull] will be removed in 1.0.0 release soon')
|
|
42
42
|
|
|
43
43
|
pullAction.apply(this, args)
|
|
44
44
|
})
|
|
@@ -49,7 +49,7 @@ hub
|
|
|
49
49
|
.description('view repository on dotenvx hub')
|
|
50
50
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
51
51
|
.action(function (...args) {
|
|
52
|
-
logger.warn('DEPRECATION
|
|
52
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub open] will be removed in 1.0.0 release soon')
|
|
53
53
|
|
|
54
54
|
openAction.apply(this, args)
|
|
55
55
|
})
|
|
@@ -60,7 +60,7 @@ hub
|
|
|
60
60
|
.description('print the auth token dotenvx hub is configured to use')
|
|
61
61
|
.option('-h, --hostname <url>', 'set hostname', 'https://hub.dotenvx.com')
|
|
62
62
|
.action(function (...args) {
|
|
63
|
-
logger.warn('DEPRECATION
|
|
63
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub token] will be removed in 1.0.0 release soon')
|
|
64
64
|
|
|
65
65
|
tokenAction.apply(this, args)
|
|
66
66
|
})
|
|
@@ -70,7 +70,7 @@ hub
|
|
|
70
70
|
.command('status')
|
|
71
71
|
.description('display logged in user')
|
|
72
72
|
.action(function (...args) {
|
|
73
|
-
logger.warn('DEPRECATION
|
|
73
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub status] will be removed in 1.0.0 release soon')
|
|
74
74
|
|
|
75
75
|
statusAction.apply(this, args)
|
|
76
76
|
})
|
|
@@ -81,7 +81,7 @@ hub
|
|
|
81
81
|
.description('log out this machine from dotenvx hub')
|
|
82
82
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
83
83
|
.action(function (...args) {
|
|
84
|
-
logger.warn('DEPRECATION
|
|
84
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx hub logout] will be removed in 1.0.0 release soon')
|
|
85
85
|
|
|
86
86
|
logoutAction.apply(this, args)
|
|
87
87
|
})
|
|
@@ -7,6 +7,11 @@ const vault = new Command('vault')
|
|
|
7
7
|
vault
|
|
8
8
|
.description('manage .env.vault files')
|
|
9
9
|
|
|
10
|
+
// dotenvx vault convert
|
|
11
|
+
vault.command('convert')
|
|
12
|
+
.description('instructions for converting .env.vault to encrypted env file(s)')
|
|
13
|
+
.action(require('./../actions/vault/convert'))
|
|
14
|
+
|
|
10
15
|
// dotenvx vault encrypt
|
|
11
16
|
vault.command('encrypt')
|
|
12
17
|
.description('encrypt .env.* to .env.vault')
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -103,6 +103,12 @@ program.command('set')
|
|
|
103
103
|
.option('-c, --encrypt', 'encrypt value')
|
|
104
104
|
.action(require('./actions/set'))
|
|
105
105
|
|
|
106
|
+
// dotenvx convert
|
|
107
|
+
program.command('convert')
|
|
108
|
+
.description('convert env file(s) to encrypted env file(s)')
|
|
109
|
+
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
110
|
+
.action(require('./actions/convert'))
|
|
111
|
+
|
|
106
112
|
// dotenvx ls
|
|
107
113
|
program.command('ls')
|
|
108
114
|
.description('print all .env files in a tree structure')
|
|
@@ -160,6 +166,7 @@ program.command('encrypt')
|
|
|
160
166
|
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
161
167
|
.action(function (...args) {
|
|
162
168
|
logger.warn('DEPRECATION NOTICE: [dotenvx encrypt] has moved. change your command to [dotenvx vault encrypt]')
|
|
169
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx encryptme] will become [dotenvx encrypt] in a 1.0.0 release scheduled for middle of June 2024.')
|
|
163
170
|
|
|
164
171
|
encryptAction.apply(this, args)
|
|
165
172
|
})
|
|
@@ -171,7 +178,7 @@ program.command('decrypt')
|
|
|
171
178
|
.argument('[directory]', 'directory to decrypt', '.')
|
|
172
179
|
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
173
180
|
.action(function (...args) {
|
|
174
|
-
logger.warn('DEPRECATION
|
|
181
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx decrypt] has moved. change your command to [dotenvx vault decrypt]')
|
|
175
182
|
|
|
176
183
|
decryptAction.apply(this, args)
|
|
177
184
|
})
|
|
@@ -31,7 +31,8 @@ function findOrCreatePublicKey (envFilepath, envKeysFilepath) {
|
|
|
31
31
|
envSrc,
|
|
32
32
|
keysSrc,
|
|
33
33
|
publicKey: envParsed[publicKeyName],
|
|
34
|
-
privateKey: keysParsed[privateKeyName]
|
|
34
|
+
privateKey: keysParsed[privateKeyName],
|
|
35
|
+
privateKeyAdded: false
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -73,7 +74,8 @@ function findOrCreatePublicKey (envFilepath, envKeysFilepath) {
|
|
|
73
74
|
envSrc,
|
|
74
75
|
keysSrc,
|
|
75
76
|
publicKey,
|
|
76
|
-
privateKey
|
|
77
|
+
privateKey,
|
|
78
|
+
privateKeyAdded: true
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const dotenv = require('dotenv')
|
|
2
|
+
|
|
3
|
+
const isEncrypted = require('./isEncrypted')
|
|
4
|
+
|
|
5
|
+
function isFullyEncrypted (src) {
|
|
6
|
+
const parsed = dotenv.parse(src)
|
|
7
|
+
|
|
8
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
9
|
+
const result = isEncrypted(key, value)
|
|
10
|
+
if (!result) {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = isFullyEncrypted
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const ignore = require('ignore')
|
|
3
|
+
|
|
4
|
+
function isIgnoringDotenvKeys () {
|
|
5
|
+
if (!fs.existsSync('.gitignore')) {
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const gitignore = fs.readFileSync('.gitignore').toString()
|
|
10
|
+
const ig = ignore(gitignore).add(gitignore)
|
|
11
|
+
|
|
12
|
+
if (!ig.ignores('.env.keys')) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = isIgnoringDotenvKeys
|
|
@@ -7,7 +7,7 @@ function replace (src, key, value) {
|
|
|
7
7
|
const parsed = dotenv.parse(src)
|
|
8
8
|
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
9
9
|
// replace
|
|
10
|
-
const regex = new RegExp(`^${key}
|
|
10
|
+
const regex = new RegExp(`^${key}=(?:(["'\`])[^\\1]*\\1|[^\\n]*)(\\n[^A-Z0-9_].*)*`, 'm')
|
|
11
11
|
output = src.replace(regex, formatted)
|
|
12
12
|
} else {
|
|
13
13
|
// append
|
package/src/lib/main.js
CHANGED
|
@@ -3,13 +3,14 @@ const dotenv = require('dotenv')
|
|
|
3
3
|
const dotenvExpand = require('dotenv-expand')
|
|
4
4
|
|
|
5
5
|
// services
|
|
6
|
-
const Encrypt = require('./services/encrypt')
|
|
7
6
|
const Ls = require('./services/ls')
|
|
8
7
|
const Get = require('./services/get')
|
|
9
8
|
const Sets = require('./services/sets')
|
|
10
9
|
const Status = require('./services/status')
|
|
10
|
+
const Encrypt = require('./services/encrypt')
|
|
11
11
|
const Genexample = require('./services/genexample')
|
|
12
12
|
const Settings = require('./services/settings')
|
|
13
|
+
const VaultEncrypt = require('./services/vaultEncrypt')
|
|
13
14
|
|
|
14
15
|
// helpers
|
|
15
16
|
const dotenvEval = require('./helpers/dotenvEval')
|
|
@@ -41,9 +42,13 @@ const parse = function (src) {
|
|
|
41
42
|
return dotenv.parse(src)
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
+
// DEPRECATED: will became the same function as convert
|
|
45
46
|
const encrypt = function (directory, envFile) {
|
|
46
|
-
return new
|
|
47
|
+
return new VaultEncrypt(directory, envFile).run()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const vaultEncrypt = function (directory, envFile) {
|
|
51
|
+
return new VaultEncrypt(directory, envFile).run()
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const ls = function (directory, envFile) {
|
|
@@ -62,6 +67,10 @@ const set = function (key, value, envFile, encrypt) {
|
|
|
62
67
|
return new Sets(key, value, envFile, encrypt).run()
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
const convert = function (envFile) {
|
|
71
|
+
return new Encrypt(envFile).run()
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
const status = function (directory) {
|
|
66
75
|
return new Status(directory).run()
|
|
67
76
|
}
|
|
@@ -96,9 +105,11 @@ module.exports = {
|
|
|
96
105
|
parse,
|
|
97
106
|
// actions related
|
|
98
107
|
encrypt,
|
|
108
|
+
vaultEncrypt,
|
|
99
109
|
ls,
|
|
100
110
|
get,
|
|
101
111
|
set,
|
|
112
|
+
convert,
|
|
102
113
|
status,
|
|
103
114
|
genexample,
|
|
104
115
|
// settings
|
|
@@ -2,91 +2,93 @@ const fs = require('fs')
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const dotenv = require('dotenv')
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
|
|
6
|
+
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
7
|
+
const encryptValue = require('./../helpers/encryptValue')
|
|
8
|
+
const isEncrypted = require('./../helpers/isEncrypted')
|
|
9
|
+
const replace = require('./../helpers/replace')
|
|
7
10
|
|
|
8
11
|
const ENCODING = 'utf8'
|
|
9
12
|
|
|
10
|
-
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
11
|
-
|
|
12
13
|
class Encrypt {
|
|
13
|
-
constructor (
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
|
|
14
|
+
constructor (envFile = '.env') {
|
|
15
|
+
this.envFile = envFile
|
|
16
|
+
this.processedEnvFiles = []
|
|
17
|
+
this.changedFilepaths = new Set()
|
|
18
|
+
this.unchangedFilepaths = new Set()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
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 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
22
|
const envFilepaths = this._envFilepaths()
|
|
36
|
-
|
|
37
|
-
// build filepaths to be passed to DotenvKeys
|
|
38
|
-
const uniqueEnvFilepaths = new Set()
|
|
39
23
|
for (const envFilepath of envFilepaths) {
|
|
40
|
-
const filepath = path.resolve(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
const filepath = path.resolve(envFilepath)
|
|
25
|
+
|
|
26
|
+
const row = {}
|
|
27
|
+
row.keys = []
|
|
28
|
+
row.filepath = filepath
|
|
29
|
+
row.envFilepath = envFilepath
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// get the original src
|
|
33
|
+
let src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
34
|
+
// get/generate the public key
|
|
35
|
+
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
36
|
+
const {
|
|
37
|
+
envSrc,
|
|
38
|
+
publicKey,
|
|
39
|
+
privateKey,
|
|
40
|
+
privateKeyAdded
|
|
41
|
+
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
42
|
+
row.publicKey = publicKey
|
|
43
|
+
row.privateKey = privateKey
|
|
44
|
+
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
45
|
+
row.privateKeyAdded = privateKeyAdded
|
|
46
|
+
|
|
47
|
+
src = envSrc // src was potentially changed by findOrCreatePublicKey so we set it again here
|
|
48
|
+
|
|
49
|
+
// track possible changes
|
|
50
|
+
row.changed = false
|
|
51
|
+
|
|
52
|
+
// iterate over all non-encrypted values and encrypt them
|
|
53
|
+
const parsed = dotenv.parse(src)
|
|
54
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
55
|
+
const encrypted = isEncrypted(key, value)
|
|
56
|
+
if (!encrypted) {
|
|
57
|
+
row.keys.push(key) // track key(s)
|
|
58
|
+
|
|
59
|
+
const encryptedValue = encryptValue(value, publicKey)
|
|
60
|
+
// once newSrc is built write it out
|
|
61
|
+
src = replace(src, key, encryptedValue)
|
|
62
|
+
|
|
63
|
+
row.changed = true // track change
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (row.changed) {
|
|
68
|
+
row.envSrc = src
|
|
69
|
+
this.changedFilepaths.add(envFilepath)
|
|
70
|
+
} else {
|
|
71
|
+
row.envSrc = src
|
|
72
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (e.code === 'ENOENT') {
|
|
76
|
+
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
77
|
+
error.code = 'MISSING_ENV_FILE'
|
|
78
|
+
|
|
79
|
+
row.error = error
|
|
80
|
+
} else {
|
|
81
|
+
row.error = e
|
|
82
|
+
}
|
|
50
83
|
}
|
|
51
84
|
|
|
52
|
-
|
|
85
|
+
this.processedEnvFiles.push(row)
|
|
53
86
|
}
|
|
54
87
|
|
|
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
88
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
addedKeys,
|
|
83
|
-
existingKeys,
|
|
84
|
-
// from DotenvVault
|
|
85
|
-
dotenvVaultFile,
|
|
86
|
-
addedVaults,
|
|
87
|
-
existingVaults,
|
|
88
|
-
addedDotenvFilenames,
|
|
89
|
-
envFile: this.envFile
|
|
89
|
+
processedEnvFiles: this.processedEnvFiles,
|
|
90
|
+
changedFilepaths: [...this.changedFilepaths],
|
|
91
|
+
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -97,22 +99,6 @@ class Encrypt {
|
|
|
97
99
|
|
|
98
100
|
return this.envFile
|
|
99
101
|
}
|
|
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
102
|
}
|
|
117
103
|
|
|
118
104
|
module.exports = Encrypt
|
|
@@ -3,7 +3,9 @@ const fs = require('fs')
|
|
|
3
3
|
const ignore = require('ignore')
|
|
4
4
|
|
|
5
5
|
const pluralize = require('./../helpers/pluralize')
|
|
6
|
+
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
|
|
6
7
|
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
|
|
8
|
+
const MISSING_GITIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
|
|
7
9
|
|
|
8
10
|
class Precommit {
|
|
9
11
|
constructor (options = {}) {
|
|
@@ -22,16 +24,20 @@ class Precommit {
|
|
|
22
24
|
}
|
|
23
25
|
} else {
|
|
24
26
|
const warnings = []
|
|
27
|
+
let successMessage = 'success'
|
|
28
|
+
let gitignore = MISSING_GITIGNORE
|
|
25
29
|
|
|
26
30
|
// 1. check for .gitignore file
|
|
27
31
|
if (!fs.existsSync('.gitignore')) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const warning = new Error('.gitignore missing')
|
|
33
|
+
warning.help = '? add it with [touch .gitignore]'
|
|
34
|
+
warnings.push(warning)
|
|
35
|
+
} else {
|
|
36
|
+
gitignore = fs.readFileSync('.gitignore').toString()
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
// 2. check .env* files against .gitignore file
|
|
34
|
-
const ig = ignore().add(
|
|
40
|
+
const ig = ignore().add(gitignore)
|
|
35
41
|
const files = fs.readdirSync(process.cwd())
|
|
36
42
|
const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
|
|
37
43
|
dotenvFiles.forEach(file => {
|
|
@@ -44,14 +50,19 @@ class Precommit {
|
|
|
44
50
|
}
|
|
45
51
|
} else {
|
|
46
52
|
if (file !== '.env.example' && file !== '.env.vault') {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const src = fs.readFileSync(file).toString()
|
|
54
|
+
const encrypted = isFullyEncrypted(src)
|
|
55
|
+
|
|
56
|
+
// if contents are encrypted don't raise an error
|
|
57
|
+
if (!encrypted) {
|
|
58
|
+
const error = new Error(`${file} not encrypted (or not gitignored)`)
|
|
59
|
+
error.help = `? encrypt it with [dotenvx protect -f ${file}] or add ${file} to .gitignore with [echo ".env*" >> .gitignore]`
|
|
60
|
+
throw error
|
|
61
|
+
}
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
64
|
})
|
|
53
65
|
|
|
54
|
-
let successMessage = 'success'
|
|
55
66
|
if (warnings.length > 0) {
|
|
56
67
|
successMessage = `success (with ${pluralize('warning', warnings.length)})`
|
|
57
68
|
}
|
package/src/lib/services/sets.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
+
const dotenv = require('dotenv')
|
|
5
|
+
|
|
4
6
|
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
|
|
7
|
+
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
5
8
|
const encryptValue = require('./../helpers/encryptValue')
|
|
6
9
|
const replace = require('./../helpers/replace')
|
|
7
10
|
|
|
@@ -14,39 +17,57 @@ class Sets {
|
|
|
14
17
|
this.envFile = envFile
|
|
15
18
|
this.encrypt = encrypt
|
|
16
19
|
|
|
17
|
-
this.publicKey = null
|
|
18
20
|
this.processedEnvFiles = []
|
|
19
|
-
this.
|
|
21
|
+
this.changedFilepaths = new Set()
|
|
22
|
+
this.unchangedFilepaths = new Set()
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
run () {
|
|
23
26
|
const envFilepaths = this._envFilepaths()
|
|
24
27
|
for (const envFilepath of envFilepaths) {
|
|
28
|
+
const filepath = path.resolve(envFilepath)
|
|
29
|
+
|
|
25
30
|
const row = {}
|
|
26
31
|
row.key = this.key
|
|
27
|
-
row.filepath = envFilepath
|
|
28
32
|
row.value = this.value
|
|
33
|
+
row.filepath = filepath
|
|
34
|
+
row.envFilepath = envFilepath
|
|
35
|
+
row.changed = false
|
|
29
36
|
|
|
30
|
-
const filepath = path.resolve(envFilepath)
|
|
31
37
|
try {
|
|
32
38
|
let value = this.value
|
|
33
39
|
let src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
40
|
+
row.originalValue = dotenv.parse(src)[row.key] || null
|
|
41
|
+
|
|
34
42
|
if (this.encrypt) {
|
|
35
43
|
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
36
44
|
const {
|
|
45
|
+
envSrc,
|
|
37
46
|
publicKey,
|
|
38
|
-
|
|
47
|
+
privateKey,
|
|
48
|
+
privateKeyAdded
|
|
39
49
|
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
40
50
|
src = envSrc // overwrite the original read (because findOrCreatePublicKey) rewrite to it
|
|
41
51
|
value = encryptValue(value, publicKey)
|
|
42
|
-
|
|
52
|
+
|
|
53
|
+
row.changed = true // track change
|
|
54
|
+
row.encryptedValue = value
|
|
43
55
|
row.publicKey = publicKey
|
|
56
|
+
row.privateKey = privateKey
|
|
57
|
+
row.privateKeyAdded = privateKeyAdded
|
|
58
|
+
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
if (value !== row.originalValue) {
|
|
62
|
+
row.envSrc = replace(src, this.key, value)
|
|
63
|
+
|
|
64
|
+
this.changedFilepaths.add(envFilepath)
|
|
65
|
+
row.changed = true
|
|
66
|
+
} else {
|
|
67
|
+
row.envSrc = src
|
|
48
68
|
|
|
49
|
-
|
|
69
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
70
|
+
}
|
|
50
71
|
} catch (e) {
|
|
51
72
|
if (e.code === 'ENOENT') {
|
|
52
73
|
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
@@ -63,7 +84,8 @@ class Sets {
|
|
|
63
84
|
|
|
64
85
|
return {
|
|
65
86
|
processedEnvFiles: this.processedEnvFiles,
|
|
66
|
-
|
|
87
|
+
changedFilepaths: [...this.changedFilepaths],
|
|
88
|
+
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
|
|
@@ -0,0 +1,118 @@
|
|
|
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 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 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
|