@dotenvx/dotenvx 0.37.1 → 0.38.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 +56 -188
- package/package.json +2 -1
- package/src/cli/actions/set.js +7 -2
- package/src/cli/actions/{decrypt.js → vault/decrypt.js} +4 -4
- package/src/cli/actions/{encrypt.js → vault/encrypt.js} +5 -5
- package/src/cli/actions/{status.js → vault/status.js} +2 -2
- package/src/cli/commands/hub.js +43 -7
- package/src/cli/commands/vault.js +31 -0
- package/src/cli/dotenvx.js +41 -22
- package/src/lib/helpers/decryptValue.js +29 -0
- package/src/lib/helpers/encryptValue.js +12 -0
- package/src/lib/helpers/findOrCreatePublicKey.js +80 -0
- package/src/lib/helpers/guessEnvironment.js +1 -0
- package/src/lib/helpers/guessPrivateKeyFilename.js +15 -0
- package/src/lib/helpers/guessPrivateKeyName.js +18 -0
- package/src/lib/helpers/guessPublicKeyName.js +18 -0
- package/src/lib/helpers/keyPair.js +15 -0
- package/src/lib/helpers/{parseExpandAndEval.js → parseDecryptEvalExpand.js} +14 -2
- package/src/lib/helpers/replace.js +26 -0
- package/src/lib/helpers/smartDotenvPrivateKey.js +30 -0
- package/src/lib/main.js +2 -2
- package/src/lib/services/run.js +26 -4
- package/src/lib/services/sets.js +21 -29
package/README.md
CHANGED
|
@@ -514,224 +514,75 @@ More examples
|
|
|
514
514
|
|
|
515
515
|
## Encryption
|
|
516
516
|
|
|
517
|
-
>
|
|
518
|
-
```sh
|
|
519
|
-
$ echo "HELLO=World" > .env
|
|
520
|
-
$ echo "HELLO=production" > .env.production
|
|
521
|
-
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
522
|
-
|
|
523
|
-
$ dotenvx encrypt
|
|
524
|
-
[dotenvx][info] encrypted to .env.vault (.env,.env.production)
|
|
525
|
-
[dotenvx][info] keys added to .env.keys (DOTENV_KEY_PRODUCTION,DOTENV_KEY_PRODUCTION)
|
|
517
|
+
> Add encryption to your `.env` files with a single command. Pass the `--encrypt` flag.
|
|
526
518
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
^ :-]
|
|
519
|
+
```sh
|
|
520
|
+
$ dotenvx set HELLO World --encrypt
|
|
521
|
+
set HELLO with encryption (.env)
|
|
531
522
|
```
|
|
532
523
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
* <details><summary>AWS Lambda</summary><br>
|
|
536
|
-
|
|
537
|
-
```sh
|
|
538
|
-
coming soon
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
</details>
|
|
542
|
-
|
|
543
|
-
* <details><summary>Digital Ocean</summary><br>
|
|
544
|
-
|
|
545
|
-
```sh
|
|
546
|
-
coming soon
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
</details>
|
|
550
|
-
|
|
551
|
-
* <details><summary>Docker 🐳</summary><br>
|
|
552
|
-
|
|
553
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
554
|
-
|
|
555
|
-
```sh
|
|
556
|
-
# Install dotenvx
|
|
557
|
-
RUN curl -fsS https://dotenvx.sh/ | sh
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
> Use it in your Dockerfile CMD
|
|
561
|
-
|
|
562
|
-
```sh
|
|
563
|
-
# Prepend dotenvx run
|
|
564
|
-
CMD ["dotenvx", "run", "--", "node", "index.js"]
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
see [docker guide](https://dotenvx.com/docs/platforms/docker)
|
|
568
|
-
|
|
569
|
-
</details>
|
|
570
|
-
|
|
571
|
-
* <details><summary>Fly.io 🎈</summary><br>
|
|
572
|
-
|
|
573
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
574
|
-
|
|
575
|
-
```sh
|
|
576
|
-
# Install dotenvx
|
|
577
|
-
RUN curl -fsS https://dotenvx.sh/ | sh
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
> Use it in your Dockerfile CMD
|
|
581
|
-
|
|
582
|
-
```sh
|
|
583
|
-
# Prepend dotenvx run
|
|
584
|
-
CMD ["dotenvx", "run", "--", "node", "index.js"]
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
see [fly guide](https://dotenvx.com/docs/platforms/fly)
|
|
588
|
-
|
|
589
|
-
</details>
|
|
590
|
-
|
|
591
|
-
* <details><summary>Heroku 🟣</summary><br>
|
|
524
|
+

|
|
592
525
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
```sh
|
|
596
|
-
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx
|
|
597
|
-
```
|
|
526
|
+
> A `DOTENV_PUBLIC_KEY` (encryption key) and a `DOTENV_PRIVATE_KEY` (decryption key) is generated using the same public-key cryptography as [Bitcoin](https://en.bitcoin.it/wiki/Secp256k1).
|
|
598
527
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
```sh
|
|
602
|
-
web: dotenvx run -- node index.js
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
see [heroku guide](https://dotenvx.com/docs/platforms/heroku)
|
|
606
|
-
|
|
607
|
-
</details>
|
|
608
|
-
|
|
609
|
-
* <details><summary>Laravel Forge</summary><br>
|
|
610
|
-
|
|
611
|
-
```sh
|
|
612
|
-
coming soon
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
</details>
|
|
616
|
-
|
|
617
|
-
* <details><summary>Netlify 🔷</summary><br>
|
|
528
|
+
More examples
|
|
618
529
|
|
|
619
|
-
|
|
530
|
+
* <details><summary>`.env`</summary><br>
|
|
620
531
|
|
|
621
532
|
```sh
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
> Use it in your `package.json scripts`
|
|
626
|
-
|
|
627
|
-
```json
|
|
628
|
-
"scripts": {
|
|
629
|
-
"dotenvx": "dotenvx",
|
|
630
|
-
"dev": "dotenvx run -- next dev --turbo",
|
|
631
|
-
"build": "dotenvx run -- next build",
|
|
632
|
-
"start": "dotenvx run -- next start"
|
|
633
|
-
},
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
see [netlify guide](https://dotenvx.com/docs/platforms/netlify)
|
|
637
|
-
|
|
638
|
-
</details>
|
|
639
|
-
|
|
640
|
-
* <details><summary>Railway 🚄</summary><br>
|
|
641
|
-
|
|
642
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
533
|
+
$ dotenvx set HELLO World --encrypt
|
|
534
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
643
535
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
536
|
+
$ dotenvx run -- node index.js
|
|
537
|
+
[dotenvx] injecting env (2) from .env
|
|
538
|
+
Hello World
|
|
647
539
|
```
|
|
648
540
|
|
|
649
|
-
|
|
541
|
+
* <details><summary>`.env.production`</summary><br>
|
|
650
542
|
|
|
651
543
|
```sh
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
see [railway guide](https://dotenvx.com/docs/platforms/railway)
|
|
657
|
-
|
|
658
|
-
</details>
|
|
659
|
-
|
|
660
|
-
* <details><summary>Render</summary><br>
|
|
544
|
+
$ dotenvx set HELLO Production --encrypt -f .env.production
|
|
545
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
661
546
|
|
|
662
|
-
|
|
663
|
-
|
|
547
|
+
$ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
|
|
548
|
+
[dotenvx] injecting env (2) from .env.production
|
|
549
|
+
Hello Production
|
|
664
550
|
```
|
|
665
551
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
* <details><summary>Vercel ▲</summary><br>
|
|
552
|
+
Note the `DOTENV_PRIVATE_KEY_PRODUCTION` ends with `_PRODUCTION`. This instructs `dotenvx run` to load the `.env.production` file.
|
|
669
553
|
|
|
670
|
-
|
|
554
|
+
* <details><summary>`.env.ci`</summary><br>
|
|
671
555
|
|
|
672
556
|
```sh
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
> Use it in your `package.json scripts`
|
|
557
|
+
$ dotenvx set HELLO Ci --encrypt -f .env.ci
|
|
558
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
677
559
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
"dev": "dotenvx run -- next dev --turbo",
|
|
682
|
-
"build": "dotenvx run -- next build",
|
|
683
|
-
"start": "dotenvx run -- next start"
|
|
684
|
-
},
|
|
560
|
+
$ DOTENV_PRIVATE_KEY_CI="<.env.ci private key>" dotenvx run -- node index.js
|
|
561
|
+
[dotenvx] injecting env (2) from .env.ci
|
|
562
|
+
Hello Ci
|
|
685
563
|
```
|
|
686
564
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
</details>
|
|
565
|
+
Note the `DOTENV_PRIVATE_KEY_CI` ends with `_CI`. This instructs `dotenvx run` to load the `.env.ci` file. See the pattern?
|
|
690
566
|
|
|
691
|
-
* <details><summary>
|
|
567
|
+
* <details><summary>combine multiple encrypted .env files</summary><br>
|
|
692
568
|
|
|
693
569
|
```sh
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
</details>
|
|
698
|
-
|
|
699
|
-
* <details><summary>GitHub Actions 🐙</summary><br>
|
|
700
|
-
|
|
701
|
-
> Add the `dotenvx` binary to GitHub Actions
|
|
570
|
+
$ dotenvx set HELLO World --encrypt -f .env
|
|
571
|
+
$ dotenvx set HELLO Production --encrypt -f .env.production
|
|
572
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
702
573
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
jobs:
|
|
707
|
-
build:
|
|
708
|
-
runs-on: ubuntu-latest
|
|
709
|
-
steps:
|
|
710
|
-
- uses: actions/checkout@v3
|
|
711
|
-
- uses: actions/setup-node@v3
|
|
712
|
-
with:
|
|
713
|
-
node-version: 16
|
|
714
|
-
- run: curl -fsS https://dotenvx.sh/ | sh
|
|
715
|
-
- run: dotenvx run -- node build.js
|
|
716
|
-
env:
|
|
717
|
-
DOTENV_KEY: ${{ secrets.DOTENV_KEY }}
|
|
574
|
+
$ DOTENV_PRIVATE_KEY="<.env private key>" DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
|
|
575
|
+
[dotenvx] injecting env (3) from .env, .env.production
|
|
576
|
+
Hello World
|
|
718
577
|
```
|
|
719
578
|
|
|
720
|
-
|
|
579
|
+
Note the `DOTENV_PRIVATE_KEY` instructs `dotenvx run` to load the `.env` file and the `DOTENV_PRIVATE_KEY_PRODUCTION` instructs it to load the `.env.production` file. See the pattern?
|
|
721
580
|
|
|
722
|
-
|
|
581
|
+
* <details><summary>other curves</summary><br>
|
|
723
582
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
> Integrate tightly with [GitHub](https://github.com) 🐙 and as a team
|
|
729
|
-
```sh
|
|
730
|
-
$ dotenvx hub login
|
|
731
|
-
$ dotenvx hub push
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
**beta**: more details coming soon.
|
|
583
|
+
> `secp256k1` is a well-known and battle tested curve, in use with Bitcoin and other cryptocurrencies, but we are open to adding support for more curves.
|
|
584
|
+
>
|
|
585
|
+
> If your organization's compliance department requires [NIST approved curves](https://csrc.nist.gov/projects/elliptic-curve-cryptography) or other curves like `curve25519`, please reach out at [security@dotenvx.com](mailto:security@dotenvx.com).
|
|
735
586
|
|
|
736
587
|
|
|
737
588
|
|
|
@@ -786,6 +637,23 @@ This fix is easy. Replace `--env-file` with `-f`.
|
|
|
786
637
|
|
|
787
638
|
[more context](https://github.com/dotenvx/dotenvx/issues/131)
|
|
788
639
|
|
|
640
|
+
#### What happened to the `.env.vault` file?
|
|
641
|
+
|
|
642
|
+
I've decided we should sunset it as a technological solution to this.
|
|
643
|
+
|
|
644
|
+
The `.env.vault` file got us far, but it had limitations such as:
|
|
645
|
+
|
|
646
|
+
* *Pull Requests* - it was difficult to tell which key had been changed
|
|
647
|
+
* *Security* - there was no mechanism to give a teammate the ability to encrypt without also giving them the ability to decrypt. Sometimes you just want to let a contractor encrypt a new value, but you don't want them to know the rest of the secrets.
|
|
648
|
+
* *Conceptual* - it takes more mental energy to understand the `.env.vault` format. Encrypted values inside a `.env` file is easier to quickly grasp.
|
|
649
|
+
* *Combining Multiple Files* - there was simply no mechanism to do this well with the `.env.vault` file format.
|
|
650
|
+
|
|
651
|
+
That said, the `.env.vault` tooling will still stick around for at least 1 year under `dotenvx vault` parent command. I'm still using it in projects as are many thousands of other people.
|
|
652
|
+
|
|
653
|
+
#### Will you provide a migration tool to quickly switch `.env.vault` files to encrypted `.env` files?
|
|
654
|
+
|
|
655
|
+
Yes. Working on this soon.
|
|
656
|
+
|
|
789
657
|
|
|
790
658
|
|
|
791
659
|
## Contributing
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.38.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"diff": "^5.2.0",
|
|
36
36
|
"dotenv": "^16.4.5",
|
|
37
37
|
"dotenv-expand": "^11.0.6",
|
|
38
|
+
"eciesjs": "^0.4.6",
|
|
38
39
|
"execa": "^5.1.1",
|
|
39
40
|
"glob": "^10.3.10",
|
|
40
41
|
"ignore": "^5.3.0",
|
package/src/cli/actions/set.js
CHANGED
|
@@ -13,7 +13,9 @@ function set (key, value) {
|
|
|
13
13
|
const {
|
|
14
14
|
processedEnvFiles,
|
|
15
15
|
settableFilepaths
|
|
16
|
-
} = main.set(key, value, options.envFile)
|
|
16
|
+
} = main.set(key, value, options.envFile, options.encrypt)
|
|
17
|
+
|
|
18
|
+
let atLeastOneSuccess = false
|
|
17
19
|
|
|
18
20
|
for (const processedEnvFile of processedEnvFiles) {
|
|
19
21
|
logger.verbose(`setting for ${processedEnvFile.filepath}`)
|
|
@@ -26,12 +28,15 @@ function set (key, value) {
|
|
|
26
28
|
logger.warn(processedEnvFile.error)
|
|
27
29
|
}
|
|
28
30
|
} else {
|
|
31
|
+
atLeastOneSuccess = true
|
|
29
32
|
logger.verbose(`${processedEnvFile.key} set`)
|
|
30
33
|
logger.debug(`${processedEnvFile.key} set to ${processedEnvFile.value}`)
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
if (atLeastOneSuccess) {
|
|
38
|
+
logger.success(`set ${key} (${settableFilepaths.join(', ')})`)
|
|
39
|
+
}
|
|
35
40
|
} catch (error) {
|
|
36
41
|
logger.error(error.message)
|
|
37
42
|
if (error.help) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
|
|
3
|
-
const logger = require('
|
|
4
|
-
const createSpinner = require('
|
|
5
|
-
const sleep = require('
|
|
3
|
+
const logger = require('./../../../shared/logger')
|
|
4
|
+
const createSpinner = require('./../../../shared/createSpinner')
|
|
5
|
+
const sleep = require('./../../../lib/helpers/sleep')
|
|
6
6
|
|
|
7
|
-
const Decrypt = require('
|
|
7
|
+
const Decrypt = require('./../../../lib/services/decrypt')
|
|
8
8
|
|
|
9
9
|
const spinner = createSpinner('decrypting')
|
|
10
10
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
-
const main = require('
|
|
5
|
-
const logger = require('
|
|
6
|
-
const createSpinner = require('
|
|
7
|
-
const sleep = require('
|
|
8
|
-
const pluralize = require('
|
|
4
|
+
const main = require('./../../../lib/main')
|
|
5
|
+
const logger = require('./../../../shared/logger')
|
|
6
|
+
const createSpinner = require('./../../../shared/createSpinner')
|
|
7
|
+
const sleep = require('./../../../lib/helpers/sleep')
|
|
8
|
+
const pluralize = require('./../../../lib/helpers/pluralize')
|
|
9
9
|
|
|
10
10
|
const spinner = createSpinner('encrypting')
|
|
11
11
|
|
package/src/cli/commands/hub.js
CHANGED
|
@@ -1,53 +1,89 @@
|
|
|
1
1
|
const { Command } = require('commander')
|
|
2
2
|
|
|
3
3
|
const store = require('./../../shared/store')
|
|
4
|
+
const logger = require('./../../shared/logger')
|
|
4
5
|
|
|
5
6
|
const hub = new Command('hub')
|
|
6
7
|
|
|
7
8
|
hub
|
|
8
9
|
.description('interact with dotenvx hub')
|
|
9
10
|
|
|
11
|
+
const loginAction = require('./../actions/hub/login')
|
|
10
12
|
hub
|
|
11
13
|
.command('login')
|
|
12
14
|
.description('authenticate to dotenvx hub')
|
|
13
15
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
14
|
-
.action(
|
|
16
|
+
.action(function (...args) {
|
|
17
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub login] will be removed in 1.0.0 release soon')
|
|
15
18
|
|
|
19
|
+
loginAction.apply(this, args)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const pushAction = require('./../actions/hub/push')
|
|
16
23
|
hub
|
|
17
24
|
.command('push')
|
|
18
25
|
.description('push .env.keys to dotenvx hub')
|
|
19
26
|
.argument('[directory]', 'directory to push', '.')
|
|
20
27
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
21
|
-
.action(
|
|
28
|
+
.action(function (...args) {
|
|
29
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub push] will be removed in 1.0.0 release soon')
|
|
30
|
+
|
|
31
|
+
pushAction.apply(this, args)
|
|
32
|
+
})
|
|
22
33
|
|
|
34
|
+
const pullAction = require('./../actions/hub/pull')
|
|
23
35
|
hub
|
|
24
36
|
.command('pull')
|
|
25
37
|
.description('pull .env.keys from dotenvx hub')
|
|
26
38
|
.argument('[directory]', 'directory to pull', '.')
|
|
27
39
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
28
|
-
.action(
|
|
40
|
+
.action(function (...args) {
|
|
41
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub pull] will be removed in 1.0.0 release soon')
|
|
29
42
|
|
|
43
|
+
pullAction.apply(this, args)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const openAction = require('./../actions/hub/open')
|
|
30
47
|
hub
|
|
31
48
|
.command('open')
|
|
32
49
|
.description('view repository on dotenvx hub')
|
|
33
50
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
34
|
-
.action(
|
|
51
|
+
.action(function (...args) {
|
|
52
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub open] will be removed in 1.0.0 release soon')
|
|
53
|
+
|
|
54
|
+
openAction.apply(this, args)
|
|
55
|
+
})
|
|
35
56
|
|
|
57
|
+
const tokenAction = require('./../actions/hub/token')
|
|
36
58
|
hub
|
|
37
59
|
.command('token')
|
|
38
60
|
.description('print the auth token dotenvx hub is configured to use')
|
|
39
61
|
.option('-h, --hostname <url>', 'set hostname', 'https://hub.dotenvx.com')
|
|
40
|
-
.action(
|
|
62
|
+
.action(function (...args) {
|
|
63
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub token] will be removed in 1.0.0 release soon')
|
|
41
64
|
|
|
65
|
+
tokenAction.apply(this, args)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const statusAction = require('./../actions/hub/status')
|
|
42
69
|
hub
|
|
43
70
|
.command('status')
|
|
44
71
|
.description('display logged in user')
|
|
45
|
-
.action(
|
|
72
|
+
.action(function (...args) {
|
|
73
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub status] will be removed in 1.0.0 release soon')
|
|
74
|
+
|
|
75
|
+
statusAction.apply(this, args)
|
|
76
|
+
})
|
|
46
77
|
|
|
78
|
+
const logoutAction = require('./../actions/hub/logout')
|
|
47
79
|
hub
|
|
48
80
|
.command('logout')
|
|
49
81
|
.description('log out this machine from dotenvx hub')
|
|
50
82
|
.option('-h, --hostname <url>', 'set hostname', store.getHostname())
|
|
51
|
-
.action(
|
|
83
|
+
.action(function (...args) {
|
|
84
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx hub logout] will be removed in 1.0.0 release soon')
|
|
85
|
+
|
|
86
|
+
logoutAction.apply(this, args)
|
|
87
|
+
})
|
|
52
88
|
|
|
53
89
|
module.exports = hub
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { Command } = require('commander')
|
|
2
|
+
|
|
3
|
+
const examples = require('./../examples')
|
|
4
|
+
|
|
5
|
+
const vault = new Command('vault')
|
|
6
|
+
|
|
7
|
+
vault
|
|
8
|
+
.description('manage .env.vault files')
|
|
9
|
+
|
|
10
|
+
// dotenvx vault encrypt
|
|
11
|
+
vault.command('encrypt')
|
|
12
|
+
.description('encrypt .env.* to .env.vault')
|
|
13
|
+
.addHelpText('after', examples.encrypt)
|
|
14
|
+
.argument('[directory]', 'directory to encrypt', '.')
|
|
15
|
+
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
16
|
+
.action(require('./../actions/vault/encrypt'))
|
|
17
|
+
|
|
18
|
+
// dotenvx vault decrypt
|
|
19
|
+
vault.command('decrypt')
|
|
20
|
+
.description('decrypt .env.vault to .env*')
|
|
21
|
+
.argument('[directory]', 'directory to decrypt', '.')
|
|
22
|
+
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
23
|
+
.action(require('./../actions/vault/decrypt'))
|
|
24
|
+
|
|
25
|
+
// dotenvx vault status
|
|
26
|
+
vault.command('status')
|
|
27
|
+
.description('compare your .env* content(s) to your .env.vault decrypted content(s)')
|
|
28
|
+
.argument('[directory]', 'directory to check status against', '.')
|
|
29
|
+
.action(require('./../actions/vault/status'))
|
|
30
|
+
|
|
31
|
+
module.exports = vault
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -12,7 +12,7 @@ const packageJson = require('./../lib/helpers/packageJson')
|
|
|
12
12
|
const notice = new UpdateNotice()
|
|
13
13
|
notice.check()
|
|
14
14
|
if (notice.update) {
|
|
15
|
-
logger.warn(`Update available ${notice.packageVersion} → ${notice.latestVersion} changelog: https://dotenvx.com/changelog`)
|
|
15
|
+
logger.warn(`Update available ${notice.packageVersion} → ${notice.latestVersion} 0.38.0 and higher have SIGNIFICANT changes. please read the changelog: https://dotenvx.com/changelog`)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// for use with run
|
|
@@ -99,29 +99,9 @@ program.command('set')
|
|
|
99
99
|
.argument('KEY', 'KEY')
|
|
100
100
|
.argument('value', 'value')
|
|
101
101
|
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
|
|
102
|
+
.option('-c, --encrypt', 'encrypt value')
|
|
102
103
|
.action(require('./actions/set'))
|
|
103
104
|
|
|
104
|
-
// dotenvx encrypt
|
|
105
|
-
program.command('encrypt')
|
|
106
|
-
.description('encrypt .env.* to .env.vault')
|
|
107
|
-
.addHelpText('after', examples.encrypt)
|
|
108
|
-
.argument('[directory]', 'directory to encrypt', '.')
|
|
109
|
-
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
110
|
-
.action(require('./actions/encrypt'))
|
|
111
|
-
|
|
112
|
-
// dotenvx decrypt
|
|
113
|
-
program.command('decrypt')
|
|
114
|
-
.description('decrypt .env.vault to .env*')
|
|
115
|
-
.argument('[directory]', 'directory to decrypt', '.')
|
|
116
|
-
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
117
|
-
.action(require('./actions/decrypt'))
|
|
118
|
-
|
|
119
|
-
// dotenvx status
|
|
120
|
-
program.command('status')
|
|
121
|
-
.description('compare your .env* content(s) to your .env.vault decrypted content(s)')
|
|
122
|
-
.argument('[directory]', 'directory to check status against', '.')
|
|
123
|
-
.action(require('./actions/status'))
|
|
124
|
-
|
|
125
105
|
// dotenvx genexample
|
|
126
106
|
program.command('genexample')
|
|
127
107
|
.description('generate .env.example')
|
|
@@ -167,6 +147,45 @@ program.command('settings')
|
|
|
167
147
|
.option('-pp, --pretty-print', 'pretty print output')
|
|
168
148
|
.action(require('./actions/settings'))
|
|
169
149
|
|
|
150
|
+
// dotenvx encrypt
|
|
151
|
+
const encryptAction = require('./actions/vault/encrypt')
|
|
152
|
+
program.command('encrypt')
|
|
153
|
+
.description('DEPRECATED: moved to [dotenvx vault encrypt]')
|
|
154
|
+
.addHelpText('after', examples.encrypt)
|
|
155
|
+
.argument('[directory]', 'directory to encrypt', '.')
|
|
156
|
+
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
|
|
157
|
+
.action(function (...args) {
|
|
158
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx encrypt] has moved. change your command to [dotenvx vault encrypt]')
|
|
159
|
+
|
|
160
|
+
encryptAction.apply(this, args)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// dotenvx decrypt
|
|
164
|
+
const decryptAction = require('./actions/vault/decrypt')
|
|
165
|
+
program.command('decrypt')
|
|
166
|
+
.description('DEPRECATED: moved to [dotenvx vault decrypt]')
|
|
167
|
+
.argument('[directory]', 'directory to decrypt', '.')
|
|
168
|
+
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
169
|
+
.action(function (...args) {
|
|
170
|
+
logger.warn('DEPRECATION NOTECE: [dotenvx decrypt] has moved. change your command to [dotenvx vault decrypt]')
|
|
171
|
+
|
|
172
|
+
decryptAction.apply(this, args)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// dotenvx status
|
|
176
|
+
const statusAction = require('./actions/vault/status')
|
|
177
|
+
program.command('status')
|
|
178
|
+
.description('DEPRECATED: moved to [dotenvx vault status]')
|
|
179
|
+
.argument('[directory]', 'directory to check status against', '.')
|
|
180
|
+
.action(function (...args) {
|
|
181
|
+
logger.warn('DEPRECATION NOTICE: [dotenvx status] has moved. change your command to [dotenvx vault status]')
|
|
182
|
+
|
|
183
|
+
statusAction.apply(this, args)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// dotenvx vault
|
|
187
|
+
program.addCommand(require('./commands/vault'))
|
|
188
|
+
|
|
170
189
|
// dotenvx hub
|
|
171
190
|
program.addCommand(require('./commands/hub'))
|
|
172
191
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { decrypt } = require('eciesjs')
|
|
2
|
+
|
|
3
|
+
const PREFIX = 'encrypted:'
|
|
4
|
+
|
|
5
|
+
function decryptValue (value, privateKey) {
|
|
6
|
+
if (!value.startsWith(PREFIX)) {
|
|
7
|
+
return value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const privateKeys = privateKey.split(',')
|
|
11
|
+
|
|
12
|
+
let decryptedValue
|
|
13
|
+
for (const key of privateKeys) {
|
|
14
|
+
const secret = Buffer.from(key, 'hex')
|
|
15
|
+
const encoded = value.substring(PREFIX.length)
|
|
16
|
+
const ciphertext = Buffer.from(encoded, 'base64')
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
decryptedValue = decrypt(secret, ciphertext).toString()
|
|
20
|
+
break
|
|
21
|
+
} catch (_error) {
|
|
22
|
+
// TODO: somehow surface these errors to the user's logs
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return decryptedValue || value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = decryptValue
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { encrypt } = require('eciesjs')
|
|
2
|
+
|
|
3
|
+
const PREFIX = 'encrypted:'
|
|
4
|
+
|
|
5
|
+
function encryptValue (value, publicKey) {
|
|
6
|
+
const ciphertext = encrypt(publicKey, Buffer.from(value))
|
|
7
|
+
const encoded = Buffer.from(ciphertext, 'hex').toString('base64') // base64 encode ciphertext
|
|
8
|
+
|
|
9
|
+
return `${PREFIX}${encoded}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = encryptValue
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
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
|
+
const ENCODING = 'utf8'
|
|
10
|
+
|
|
11
|
+
function findOrCreatePublicKey (envFilepath, envKeysFilepath) {
|
|
12
|
+
// filename
|
|
13
|
+
const filename = path.basename(envFilepath)
|
|
14
|
+
const publicKeyName = guessPublicKeyName(envFilepath)
|
|
15
|
+
const privateKeyName = guessPrivateKeyName(envFilepath)
|
|
16
|
+
|
|
17
|
+
// src
|
|
18
|
+
let envSrc = fs.readFileSync(envFilepath, { encoding: ENCODING })
|
|
19
|
+
let keysSrc = ''
|
|
20
|
+
if (fs.existsSync(envKeysFilepath)) {
|
|
21
|
+
keysSrc = fs.readFileSync(envKeysFilepath, { encoding: ENCODING })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// parsed
|
|
25
|
+
const envParsed = dotenv.parse(envSrc)
|
|
26
|
+
const keysParsed = dotenv.parse(keysSrc)
|
|
27
|
+
|
|
28
|
+
// if DOTENV_PUBLIC_KEY_${environment} already present then go no further
|
|
29
|
+
if (envParsed[publicKeyName] && envParsed[publicKeyName].length > 0) {
|
|
30
|
+
return {
|
|
31
|
+
envSrc,
|
|
32
|
+
keysSrc,
|
|
33
|
+
publicKey: envParsed[publicKeyName],
|
|
34
|
+
privateKey: keysParsed[privateKeyName]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// generate key pair
|
|
39
|
+
const { publicKey, privateKey } = keyPair()
|
|
40
|
+
|
|
41
|
+
// publicKey
|
|
42
|
+
const prependPublicKey = [
|
|
43
|
+
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
|
|
44
|
+
'#/ public-key encryption for .env files /',
|
|
45
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
46
|
+
'#/----------------------------------------------------------/',
|
|
47
|
+
`${publicKeyName}="${publicKey}"`,
|
|
48
|
+
'',
|
|
49
|
+
`# ${filename}`
|
|
50
|
+
].join('\n')
|
|
51
|
+
|
|
52
|
+
// privateKey
|
|
53
|
+
const firstTimeKeysSrc = [
|
|
54
|
+
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
|
|
55
|
+
'#/ private decryption keys. DO NOT commit to source control /',
|
|
56
|
+
'#/ [how it works](https://dotenvx.com/encryption) /',
|
|
57
|
+
'#/----------------------------------------------------------/'
|
|
58
|
+
].join('\n')
|
|
59
|
+
const appendPrivateKey = [
|
|
60
|
+
`# ${filename}`,
|
|
61
|
+
`${privateKeyName}="${privateKey}"`,
|
|
62
|
+
''
|
|
63
|
+
].join('\n')
|
|
64
|
+
|
|
65
|
+
envSrc = `${prependPublicKey}\n${envSrc}`
|
|
66
|
+
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n`
|
|
67
|
+
keysSrc = `${keysSrc}\n${appendPrivateKey}`
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(envFilepath, envSrc)
|
|
70
|
+
fs.writeFileSync(envKeysFilepath, keysSrc)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
envSrc,
|
|
74
|
+
keysSrc,
|
|
75
|
+
publicKey,
|
|
76
|
+
privateKey
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = findOrCreatePublicKey
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const PREFIX = 'DOTENV_PRIVATE_KEY'
|
|
2
|
+
|
|
3
|
+
function guessPrivateKeyFilename (privateKeyName) {
|
|
4
|
+
// .env
|
|
5
|
+
if (privateKeyName === PREFIX) {
|
|
6
|
+
return '.env'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const filenameSuffix = privateKeyName.substring(`${PREFIX}_`.length).split('_').join('.').toLowerCase()
|
|
10
|
+
// .env.ENVIRONMENT
|
|
11
|
+
|
|
12
|
+
return `.env.${filenameSuffix}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = guessPrivateKeyFilename
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const guessEnvironment = require('./guessEnvironment')
|
|
3
|
+
|
|
4
|
+
function guessPrivateKeyName (filepath) {
|
|
5
|
+
const filename = path.basename(filepath).toLowerCase()
|
|
6
|
+
|
|
7
|
+
// .env
|
|
8
|
+
if (filename === '.env') {
|
|
9
|
+
return 'DOTENV_PRIVATE_KEY'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// .env.ENVIRONMENT
|
|
13
|
+
const environment = guessEnvironment(filename)
|
|
14
|
+
|
|
15
|
+
return `DOTENV_PRIVATE_KEY_${environment.toUpperCase()}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = guessPrivateKeyName
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const guessEnvironment = require('./guessEnvironment')
|
|
3
|
+
|
|
4
|
+
function guessPublicKeyName (filepath) {
|
|
5
|
+
const filename = path.basename(filepath).toLowerCase()
|
|
6
|
+
|
|
7
|
+
// .env
|
|
8
|
+
if (filename === '.env') {
|
|
9
|
+
return 'DOTENV_PUBLIC_KEY'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// .env.ENVIRONMENT
|
|
13
|
+
const environment = guessEnvironment(filename)
|
|
14
|
+
|
|
15
|
+
return `DOTENV_PUBLIC_KEY_${environment.toUpperCase()}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = guessPublicKeyName
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { PrivateKey } = require('eciesjs')
|
|
2
|
+
|
|
3
|
+
function keyPair () {
|
|
4
|
+
const kp = new PrivateKey()
|
|
5
|
+
|
|
6
|
+
const publicKey = kp.publicKey.toHex()
|
|
7
|
+
const privateKey = kp.secret.toString('hex')
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
publicKey,
|
|
11
|
+
privateKey
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = keyPair
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
const dotenv = require('dotenv')
|
|
2
2
|
const dotenvExpand = require('dotenv-expand')
|
|
3
3
|
const dotenvEval = require('./dotenvEval')
|
|
4
|
+
const decryptValue = require('./decryptValue')
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
+
function parseDecryptEvalExpand (src, privateKey = null) {
|
|
6
7
|
// parse
|
|
7
8
|
const parsed = dotenv.parse(src)
|
|
8
9
|
|
|
10
|
+
// inline decrypt
|
|
11
|
+
for (const key in parsed) {
|
|
12
|
+
const value = parsed[key]
|
|
13
|
+
|
|
14
|
+
// handle inline encrypted values
|
|
15
|
+
if (privateKey && privateKey.length > 0) {
|
|
16
|
+
// privateKey
|
|
17
|
+
parsed[key] = decryptValue(value, privateKey)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
// eval parsed only. do NOT eval process.env ever. too risky/dangerous.
|
|
10
22
|
const inputParsed = {
|
|
11
23
|
processEnv: {},
|
|
@@ -28,4 +40,4 @@ function parseExpandAndEval (src) {
|
|
|
28
40
|
return result
|
|
29
41
|
}
|
|
30
42
|
|
|
31
|
-
module.exports =
|
|
43
|
+
module.exports = parseDecryptEvalExpand
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const dotenv = require('dotenv')
|
|
2
|
+
|
|
3
|
+
function replace (src, key, value) {
|
|
4
|
+
let output
|
|
5
|
+
let formatted = `${key}="${value}"`
|
|
6
|
+
|
|
7
|
+
const parsed = dotenv.parse(src)
|
|
8
|
+
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
9
|
+
// replace
|
|
10
|
+
const regex = new RegExp(`^${key}=.*$`, 'm') // Regular expression to find the key and replace its value
|
|
11
|
+
output = src.replace(regex, formatted)
|
|
12
|
+
} else {
|
|
13
|
+
// append
|
|
14
|
+
if (src.endsWith('\n')) {
|
|
15
|
+
formatted = formatted + '\n'
|
|
16
|
+
} else {
|
|
17
|
+
formatted = '\n' + formatted
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
output = src + formatted
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return output
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = replace
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const dotenv = require('dotenv')
|
|
4
|
+
|
|
5
|
+
const guessPrivateKeyName = require('./guessPrivateKeyName')
|
|
6
|
+
|
|
7
|
+
function smartDotenvPrivateKey (envFilepath) {
|
|
8
|
+
const privateKeyName = guessPrivateKeyName(envFilepath) // DOTENV_PRIVATE_KEY_${ENVIRONMENT}
|
|
9
|
+
|
|
10
|
+
// process.env wins
|
|
11
|
+
if (process.env[privateKeyName] && process.env[privateKeyName].length > 0) {
|
|
12
|
+
return process.env[privateKeyName]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// fallback to presence of .env.keys - path/to/.env.keys
|
|
16
|
+
const directory = path.dirname(envFilepath)
|
|
17
|
+
const envKeysFilepath = path.resolve(directory, '.env.keys')
|
|
18
|
+
if (fs.existsSync(envKeysFilepath)) {
|
|
19
|
+
const keysSrc = fs.readFileSync(envKeysFilepath)
|
|
20
|
+
const keysParsed = dotenv.parse(keysSrc)
|
|
21
|
+
|
|
22
|
+
if (keysParsed[privateKeyName] && keysParsed[privateKeyName].length > 0) {
|
|
23
|
+
return keysParsed[privateKeyName]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = smartDotenvPrivateKey
|
package/src/lib/main.js
CHANGED
|
@@ -58,8 +58,8 @@ const get = function (key, envs = [], overload = false, DOTENV_KEY = '', all = f
|
|
|
58
58
|
return new Get(key, envs, overload, DOTENV_KEY, all).run()
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const set = function (key, value, envFile) {
|
|
62
|
-
return new Sets(key, value, envFile).run()
|
|
61
|
+
const set = function (key, value, envFile, encrypt) {
|
|
62
|
+
return new Sets(key, value, envFile, encrypt).run()
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const status = function (directory) {
|
package/src/lib/services/run.js
CHANGED
|
@@ -11,11 +11,14 @@ const DEFAULT_ENV_VAULTS = [{ type: TYPE_ENV_VAULT_FILE, value: '.env.vault' }]
|
|
|
11
11
|
|
|
12
12
|
const inject = require('./../helpers/inject')
|
|
13
13
|
const decrypt = require('./../helpers/decrypt')
|
|
14
|
-
const
|
|
14
|
+
const parseDecryptEvalExpand = require('./../helpers/parseDecryptEvalExpand')
|
|
15
15
|
const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
|
|
16
|
+
const smartDotenvPrivateKey = require('./../helpers/smartDotenvPrivateKey')
|
|
17
|
+
const guessPrivateKeyFilename = require('./../helpers/guessPrivateKeyFilename')
|
|
16
18
|
|
|
17
19
|
class Run {
|
|
18
20
|
constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env) {
|
|
21
|
+
this.dotenvPrivateKeyNames = Object.keys(processEnv).filter(key => key.startsWith('DOTENV_PRIVATE_KEY')) // important, must be first. used by determineEnvs
|
|
19
22
|
this.envs = this._determineEnvs(envs, DOTENV_KEY)
|
|
20
23
|
this.overload = overload
|
|
21
24
|
this.DOTENV_KEY = DOTENV_KEY
|
|
@@ -35,6 +38,7 @@ class Run {
|
|
|
35
38
|
// { type: 'envFile', value: '.env' },
|
|
36
39
|
// { type: 'env', value: 'HELLO=three' }
|
|
37
40
|
// ]
|
|
41
|
+
|
|
38
42
|
for (const env of this.envs) {
|
|
39
43
|
if (env.type === TYPE_ENV_VAULT_FILE) {
|
|
40
44
|
this._injectEnvVaultFile(env.value)
|
|
@@ -59,7 +63,7 @@ class Run {
|
|
|
59
63
|
row.string = env
|
|
60
64
|
|
|
61
65
|
try {
|
|
62
|
-
const parsed =
|
|
66
|
+
const parsed = parseDecryptEvalExpand(env)
|
|
63
67
|
row.parsed = parsed
|
|
64
68
|
this.readableStrings.add(env)
|
|
65
69
|
|
|
@@ -87,7 +91,9 @@ class Run {
|
|
|
87
91
|
const src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
88
92
|
this.readableFilepaths.add(envFilepath)
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
// if DOTENV_PRIVATE_KEY_* already set in process.env then use it
|
|
95
|
+
const privateKey = smartDotenvPrivateKey(envFilepath)
|
|
96
|
+
const parsed = parseDecryptEvalExpand(src, privateKey)
|
|
91
97
|
row.parsed = parsed
|
|
92
98
|
|
|
93
99
|
const { injected, preExisted } = this._inject(this.processEnv, parsed, this.overload)
|
|
@@ -156,7 +162,7 @@ class Run {
|
|
|
156
162
|
|
|
157
163
|
try {
|
|
158
164
|
// parse this. it's the equivalent of the .env file
|
|
159
|
-
const parsed =
|
|
165
|
+
const parsed = parseDecryptEvalExpand(decrypted)
|
|
160
166
|
row.parsed = parsed
|
|
161
167
|
|
|
162
168
|
const { injected, preExisted } = this._inject(this.processEnv, parsed, this.overload)
|
|
@@ -177,8 +183,24 @@ class Run {
|
|
|
177
183
|
return inject(processEnv, parsed, overload)
|
|
178
184
|
}
|
|
179
185
|
|
|
186
|
+
_determineEnvsFromDotenvPrivateKey () {
|
|
187
|
+
const envs = []
|
|
188
|
+
|
|
189
|
+
for (const privateKeyName of this.dotenvPrivateKeyNames) {
|
|
190
|
+
const filename = guessPrivateKeyFilename(privateKeyName)
|
|
191
|
+
envs.push({ type: TYPE_ENV_FILE, value: filename })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return envs
|
|
195
|
+
}
|
|
196
|
+
|
|
180
197
|
_determineEnvs (envs = [], DOTENV_KEY = '') {
|
|
181
198
|
if (!envs || envs.length <= 0) {
|
|
199
|
+
// if process.env.DOTENV_PRIVATE_KEY or process.env.DOTENV_PRIVATE_KEY_${environment} is set, assume inline encryption methodology
|
|
200
|
+
if (this.dotenvPrivateKeyNames.length > 0) {
|
|
201
|
+
return this._determineEnvsFromDotenvPrivateKey()
|
|
202
|
+
}
|
|
203
|
+
|
|
182
204
|
if (DOTENV_KEY.length > 0) {
|
|
183
205
|
// if DOTENV_KEY is set then default to look for .env.vault file
|
|
184
206
|
return DEFAULT_ENV_VAULTS
|
package/src/lib/services/sets.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
|
|
5
|
+
const encryptValue = require('./../helpers/encryptValue')
|
|
6
|
+
const replace = require('./../helpers/replace')
|
|
4
7
|
|
|
5
8
|
const ENCODING = 'utf8'
|
|
6
9
|
|
|
7
10
|
class Sets {
|
|
8
|
-
constructor (key, value, envFile = '.env') {
|
|
11
|
+
constructor (key, value, envFile = '.env', encrypt = false) {
|
|
9
12
|
this.key = key
|
|
10
13
|
this.value = value
|
|
11
14
|
this.envFile = envFile
|
|
15
|
+
this.encrypt = encrypt
|
|
12
16
|
|
|
17
|
+
this.publicKey = null
|
|
13
18
|
this.processedEnvFiles = []
|
|
14
19
|
this.settableFilepaths = new Set()
|
|
15
20
|
}
|
|
@@ -19,21 +24,26 @@ class Sets {
|
|
|
19
24
|
for (const envFilepath of envFilepaths) {
|
|
20
25
|
const row = {}
|
|
21
26
|
row.key = this.key
|
|
22
|
-
row.value = this.value
|
|
23
27
|
row.filepath = envFilepath
|
|
28
|
+
row.value = this.value
|
|
24
29
|
|
|
25
30
|
const filepath = path.resolve(envFilepath)
|
|
26
31
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
let value = this.value
|
|
33
|
+
let src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
34
|
+
if (this.encrypt) {
|
|
35
|
+
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
36
|
+
const {
|
|
37
|
+
publicKey,
|
|
38
|
+
envSrc
|
|
39
|
+
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
40
|
+
src = envSrc // overwrite the original read (because findOrCreatePublicKey) rewrite to it
|
|
41
|
+
value = encryptValue(value, publicKey)
|
|
42
|
+
row.encryptedValue = value // useful
|
|
43
|
+
row.publicKey = publicKey
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
const newSrc = replace(src, this.key, value)
|
|
37
47
|
fs.writeFileSync(filepath, newSrc)
|
|
38
48
|
|
|
39
49
|
this.settableFilepaths.add(envFilepath)
|
|
@@ -64,24 +74,6 @@ class Sets {
|
|
|
64
74
|
|
|
65
75
|
return this.envFile
|
|
66
76
|
}
|
|
67
|
-
|
|
68
|
-
_srcReplaced (src) {
|
|
69
|
-
// Regular expression to find the key and replace its value
|
|
70
|
-
const regex = new RegExp(`^${this.key}=.*$`, 'm')
|
|
71
|
-
|
|
72
|
-
return src.replace(regex, `${this.key}="${this.value}"`)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_srcAppended (src) {
|
|
76
|
-
let formatted = `${this.key}="${this.value}"`
|
|
77
|
-
if (src.endsWith('\n')) {
|
|
78
|
-
formatted = formatted + '\n'
|
|
79
|
-
} else {
|
|
80
|
-
formatted = '\n' + formatted
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return src + formatted
|
|
84
|
-
}
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
module.exports = Sets
|