@dotenvx/dotenvx 0.37.0 → 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 +69 -183
- 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} +13 -3
- 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/src/lib/services/status.js +11 -2
package/README.md
CHANGED
|
@@ -490,230 +490,99 @@ More examples
|
|
|
490
490
|
Available log levels are `error, warn, info, verbose, debug, silly`
|
|
491
491
|
|
|
492
492
|
</details>
|
|
493
|
+
* <details><summary>`--convention` flag</summary><br>
|
|
493
494
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
## Encryption
|
|
498
|
-
|
|
499
|
-
> Encrypt your secrets to a `.env.vault` file and load from it (recommended for production and ci).
|
|
500
|
-
```sh
|
|
501
|
-
$ echo "HELLO=World" > .env
|
|
502
|
-
$ echo "HELLO=production" > .env.production
|
|
503
|
-
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
504
|
-
|
|
505
|
-
$ dotenvx encrypt
|
|
506
|
-
[dotenvx][info] encrypted to .env.vault (.env,.env.production)
|
|
507
|
-
[dotenvx][info] keys added to .env.keys (DOTENV_KEY_PRODUCTION,DOTENV_KEY_PRODUCTION)
|
|
508
|
-
|
|
509
|
-
$ DOTENV_KEY='<dotenv_key_production>' dotenvx run -- node index.js
|
|
510
|
-
[dotenvx][info] loading env (1) from encrypted .env.vault
|
|
511
|
-
Hello production
|
|
512
|
-
^ :-]
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
More examples
|
|
516
|
-
|
|
517
|
-
* <details><summary>AWS Lambda</summary><br>
|
|
518
|
-
|
|
519
|
-
```sh
|
|
520
|
-
coming soon
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
</details>
|
|
524
|
-
|
|
525
|
-
* <details><summary>Digital Ocean</summary><br>
|
|
526
|
-
|
|
527
|
-
```sh
|
|
528
|
-
coming soon
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
</details>
|
|
532
|
-
|
|
533
|
-
* <details><summary>Docker 🐳</summary><br>
|
|
534
|
-
|
|
535
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
536
|
-
|
|
537
|
-
```sh
|
|
538
|
-
# Install dotenvx
|
|
539
|
-
RUN curl -fsS https://dotenvx.sh/ | sh
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
> Use it in your Dockerfile CMD
|
|
495
|
+
Want to load envs conveniently usng the same convention as Next.js? Set `--convention` to `nextjs`:
|
|
543
496
|
|
|
544
497
|
```sh
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
see [docker guide](https://dotenvx.com/docs/platforms/docker)
|
|
550
|
-
|
|
551
|
-
</details>
|
|
552
|
-
|
|
553
|
-
* <details><summary>Fly.io 🎈</summary><br>
|
|
554
|
-
|
|
555
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
498
|
+
$ echo "HELLO=development local" > .env.development.local
|
|
499
|
+
$ echo "HELLO=local" > .env.local
|
|
500
|
+
$ echo "HELLO=development" > .env.development
|
|
501
|
+
$ echo "HELLO=env" > .env
|
|
556
502
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
RUN curl -fsS https://dotenvx.sh/ | sh
|
|
503
|
+
$ dotenvx run --convention=nextjs -- node index.js
|
|
504
|
+
Hello development local
|
|
560
505
|
```
|
|
561
506
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
```sh
|
|
565
|
-
# Prepend dotenvx run
|
|
566
|
-
CMD ["dotenvx", "run", "--", "node", "index.js"]
|
|
567
|
-
```
|
|
507
|
+
See [next.js environment variable load order](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#environment-variable-load-order)
|
|
568
508
|
|
|
569
|
-
|
|
509
|
+
(more conventions available upon request)
|
|
570
510
|
|
|
571
511
|
</details>
|
|
572
512
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
> Add the buildpack, installing the `dotenvx` binary to your heroku deployment.
|
|
576
|
-
|
|
577
|
-
```sh
|
|
578
|
-
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
> Use it in your Procfile.
|
|
582
|
-
|
|
583
|
-
```sh
|
|
584
|
-
web: dotenvx run -- node index.js
|
|
585
|
-
```
|
|
513
|
+
|
|
586
514
|
|
|
587
|
-
|
|
515
|
+
## Encryption
|
|
588
516
|
|
|
589
|
-
|
|
517
|
+
> Add encryption to your `.env` files with a single command. Pass the `--encrypt` flag.
|
|
590
518
|
|
|
591
|
-
|
|
519
|
+
```sh
|
|
520
|
+
$ dotenvx set HELLO World --encrypt
|
|
521
|
+
set HELLO with encryption (.env)
|
|
522
|
+
```
|
|
592
523
|
|
|
593
|
-
|
|
594
|
-
coming soon
|
|
595
|
-
```
|
|
524
|
+

|
|
596
525
|
|
|
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
|
-
|
|
528
|
+
More examples
|
|
600
529
|
|
|
601
|
-
|
|
530
|
+
* <details><summary>`.env`</summary><br>
|
|
602
531
|
|
|
603
532
|
```sh
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
> Use it in your `package.json scripts`
|
|
608
|
-
|
|
609
|
-
```json
|
|
610
|
-
"scripts": {
|
|
611
|
-
"dotenvx": "dotenvx",
|
|
612
|
-
"dev": "dotenvx run -- next dev --turbo",
|
|
613
|
-
"build": "dotenvx run -- next build",
|
|
614
|
-
"start": "dotenvx run -- next start"
|
|
615
|
-
},
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
see [netlify guide](https://dotenvx.com/docs/platforms/netlify)
|
|
619
|
-
|
|
620
|
-
</details>
|
|
621
|
-
|
|
622
|
-
* <details><summary>Railway 🚄</summary><br>
|
|
623
|
-
|
|
624
|
-
> Add the `dotenvx` binary to your Dockerfile
|
|
533
|
+
$ dotenvx set HELLO World --encrypt
|
|
534
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
625
535
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
536
|
+
$ dotenvx run -- node index.js
|
|
537
|
+
[dotenvx] injecting env (2) from .env
|
|
538
|
+
Hello World
|
|
629
539
|
```
|
|
630
540
|
|
|
631
|
-
|
|
541
|
+
* <details><summary>`.env.production`</summary><br>
|
|
632
542
|
|
|
633
543
|
```sh
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
see [railway guide](https://dotenvx.com/docs/platforms/railway)
|
|
639
|
-
|
|
640
|
-
</details>
|
|
641
|
-
|
|
642
|
-
* <details><summary>Render</summary><br>
|
|
544
|
+
$ dotenvx set HELLO Production --encrypt -f .env.production
|
|
545
|
+
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
|
|
643
546
|
|
|
644
|
-
|
|
645
|
-
|
|
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
|
|
646
550
|
```
|
|
647
551
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
* <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.
|
|
651
553
|
|
|
652
|
-
|
|
554
|
+
* <details><summary>`.env.ci`</summary><br>
|
|
653
555
|
|
|
654
556
|
```sh
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
> 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
|
|
659
559
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
"dev": "dotenvx run -- next dev --turbo",
|
|
664
|
-
"build": "dotenvx run -- next build",
|
|
665
|
-
"start": "dotenvx run -- next start"
|
|
666
|
-
},
|
|
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
|
|
667
563
|
```
|
|
668
564
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
</details>
|
|
565
|
+
Note the `DOTENV_PRIVATE_KEY_CI` ends with `_CI`. This instructs `dotenvx run` to load the `.env.ci` file. See the pattern?
|
|
672
566
|
|
|
673
|
-
* <details><summary>
|
|
567
|
+
* <details><summary>combine multiple encrypted .env files</summary><br>
|
|
674
568
|
|
|
675
569
|
```sh
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
</details>
|
|
680
|
-
|
|
681
|
-
* <details><summary>GitHub Actions 🐙</summary><br>
|
|
682
|
-
|
|
683
|
-
> 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
|
|
684
573
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
jobs:
|
|
689
|
-
build:
|
|
690
|
-
runs-on: ubuntu-latest
|
|
691
|
-
steps:
|
|
692
|
-
- uses: actions/checkout@v3
|
|
693
|
-
- uses: actions/setup-node@v3
|
|
694
|
-
with:
|
|
695
|
-
node-version: 16
|
|
696
|
-
- run: curl -fsS https://dotenvx.sh/ | sh
|
|
697
|
-
- run: dotenvx run -- node build.js
|
|
698
|
-
env:
|
|
699
|
-
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
|
|
700
577
|
```
|
|
701
578
|
|
|
702
|
-
|
|
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?
|
|
703
580
|
|
|
704
|
-
|
|
581
|
+
* <details><summary>other curves</summary><br>
|
|
705
582
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
> Integrate tightly with [GitHub](https://github.com) 🐙 and as a team
|
|
711
|
-
```sh
|
|
712
|
-
$ dotenvx hub login
|
|
713
|
-
$ dotenvx hub push
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
**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).
|
|
717
586
|
|
|
718
587
|
|
|
719
588
|
|
|
@@ -768,6 +637,23 @@ This fix is easy. Replace `--env-file` with `-f`.
|
|
|
768
637
|
|
|
769
638
|
[more context](https://github.com/dotenvx/dotenvx/issues/131)
|
|
770
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
|
+
|
|
771
657
|
|
|
772
658
|
|
|
773
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const logger = require('
|
|
1
|
+
const logger = require('./../../../shared/logger')
|
|
2
2
|
|
|
3
|
-
const main = require('
|
|
3
|
+
const main = require('./../../../lib/main')
|
|
4
4
|
|
|
5
5
|
function status (directory) {
|
|
6
6
|
// debug args
|
|
@@ -10,10 +10,11 @@ function status (directory) {
|
|
|
10
10
|
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
const { changes, nochanges } = main.status(directory)
|
|
13
|
+
const { changes, nochanges, untracked } = main.status(directory)
|
|
14
14
|
|
|
15
15
|
const changeFilenames = []
|
|
16
16
|
const nochangeFilenames = []
|
|
17
|
+
const untrackedFilenames = []
|
|
17
18
|
|
|
18
19
|
for (const row of nochanges) {
|
|
19
20
|
nochangeFilenames.push(row.filename)
|
|
@@ -50,6 +51,15 @@ function status (directory) {
|
|
|
50
51
|
logger.warn('no .env* files.')
|
|
51
52
|
logger.help('? add one with [echo "HELLO=World" > .env] and then run [dotenvx status]')
|
|
52
53
|
}
|
|
54
|
+
|
|
55
|
+
for (const row of untracked) {
|
|
56
|
+
untrackedFilenames.push(row.filename)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (untrackedFilenames.length > 0) {
|
|
60
|
+
logger.warn(`untracked (${untrackedFilenames.join(', ')})`)
|
|
61
|
+
logger.help(`? track them with [dotenvx encrypt ${directory}]`)
|
|
62
|
+
}
|
|
53
63
|
} catch (error) {
|
|
54
64
|
logger.error(error.message)
|
|
55
65
|
if (error.help) {
|
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
|
|
@@ -16,6 +16,7 @@ class Status {
|
|
|
16
16
|
this.directory = directory
|
|
17
17
|
this.changes = []
|
|
18
18
|
this.nochanges = []
|
|
19
|
+
this.untracked = [] // not tracked in .env.vault
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
run () {
|
|
@@ -60,7 +61,14 @@ class Status {
|
|
|
60
61
|
|
|
61
62
|
// grab decrypted
|
|
62
63
|
const { processedEnvs } = new Decrypt(this.directory, row.environment).run()
|
|
63
|
-
|
|
64
|
+
const result = processedEnvs[0]
|
|
65
|
+
|
|
66
|
+
// handle warnings
|
|
67
|
+
row.decrypted = result.decrypted
|
|
68
|
+
if (result.warning) {
|
|
69
|
+
this.untracked.push(row)
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
64
72
|
|
|
65
73
|
// differences
|
|
66
74
|
row.differences = diff.diffWords(row.decrypted, row.raw)
|
|
@@ -78,7 +86,8 @@ class Status {
|
|
|
78
86
|
|
|
79
87
|
return {
|
|
80
88
|
changes: this.changes,
|
|
81
|
-
nochanges: this.nochanges
|
|
89
|
+
nochanges: this.nochanges,
|
|
90
|
+
untracked: this.untracked
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|