@dotenvx/dotenvx 2.26.1 → 2.27.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/CHANGELOG.md +7 -1
- package/README.md +48 -40
- package/package.json +12 -2
- package/src/cli/actions/decrypt.js +3 -3
- package/src/cli/actions/encrypt.js +3 -3
- package/src/cli/actions/get.js +2 -2
- package/src/cli/actions/keypair.js +1 -1
- package/src/cli/actions/run.js +46 -42
- package/src/cli/actions/set.js +2 -2
- package/src/cli/actions/validate.js +21 -33
- package/src/cli/dotenvx.js +2 -2
- package/src/lib/grammars/envfile.peggy +98 -0
- package/src/lib/helpers/encryptedSources.js +15 -0
- package/src/lib/helpers/envfileParser.js +2731 -0
- package/src/lib/helpers/errors.js +16 -16
- package/src/lib/helpers/executeCommand.js +6 -2
- package/src/lib/helpers/formatEnvfileSyntaxError.js +24 -0
- package/src/lib/helpers/isValidEmail.js +11 -0
- package/src/lib/helpers/isValidUrl.js +9 -0
- package/src/lib/helpers/parseWithDecryptor.js +9 -1
- package/src/lib/helpers/readEnvfile.js +128 -0
- package/src/lib/helpers/selectKeyStorage.js +1 -1
- package/src/lib/helpers/validate.js +83 -14
- package/src/lib/helpers/validateEnvfile.js +24 -0
- package/src/lib/main.js +7 -7
- package/src/lib/providers/index.js +1 -1
- package/src/lib/proxy/configureProxy.js +55 -0
- package/src/lib/proxy/eligibleHeaders.js +16 -0
- package/src/lib/proxy/prepareProxy.js +24 -0
- package/src/lib/proxy/proxyCertificates.js +44 -0
- package/src/lib/proxy/proxyForward.js +65 -0
- package/src/lib/proxy/proxyPreload.js +21 -0
- package/src/lib/proxy/proxyPreloadSource.js +3 -0
- package/src/lib/proxy/proxyServer.js +175 -0
- package/src/lib/resolvers/envs.js +15 -5
- package/src/lib/resolvers/get.js +1 -1
- package/src/lib/services/validate.js +58 -0
- package/src/lib/transforms/set.js +2 -2
- package/src/lib/helpers/validateEnvExample.js +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.27.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.27.0](https://github.com/dotenvx/dotenvx/compare/v2.26.1...v2.27.0) (2026-09-16)
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
* BREAKING: Removed `run --validate` flag and `validate` command against .env.example file. To be replaced soon with a modern validation scheme.
|
|
6
12
|
|
|
7
13
|
## [2.26.1](https://github.com/dotenvx/dotenvx/compare/v2.26.0...v2.26.1) (2026-09-15)
|
|
8
14
|
|
package/README.md
CHANGED
|
@@ -1565,29 +1565,44 @@ Hello production
|
|
|
1565
1565
|
Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
|
|
1566
1566
|
|
|
1567
1567
|
</details>
|
|
1568
|
-
<details><summary>`run
|
|
1568
|
+
<details><summary>`run` with an Envfile</summary><br>
|
|
1569
1569
|
|
|
1570
|
-
|
|
1570
|
+
When an `Envfile` is present, `run` automatically validates the resolved environment before starting your command. No validation flag is needed.
|
|
1571
1571
|
|
|
1572
|
-
```
|
|
1573
|
-
#
|
|
1574
|
-
DATABASE_URL
|
|
1575
|
-
API_KEY
|
|
1576
|
-
SENTRY_DSN
|
|
1572
|
+
```ruby
|
|
1573
|
+
# Envfile
|
|
1574
|
+
env "DATABASE_URL", type: "url"
|
|
1575
|
+
env "API_KEY"
|
|
1576
|
+
env "SENTRY_DSN", optional: true
|
|
1577
1577
|
```
|
|
1578
1578
|
|
|
1579
1579
|
```sh
|
|
1580
|
-
$ dotenvx run --
|
|
1581
|
-
[
|
|
1580
|
+
$ dotenvx run -- node index.js
|
|
1581
|
+
[INVALID_ENV] DATABASE_URL is required; API_KEY is required
|
|
1582
1582
|
```
|
|
1583
1583
|
|
|
1584
|
-
|
|
1584
|
+
Envfile validation failures stop the command. Other loading errors require `--strict` to stop execution.
|
|
1585
1585
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1586
|
+
Use exact file blocks to override rules for selected files:
|
|
1587
|
+
|
|
1588
|
+
```ruby
|
|
1589
|
+
env "HELLO"
|
|
1590
|
+
env "PORT", type: "port"
|
|
1591
|
+
env "STRIPE_SECRET_KEY", optional: true
|
|
1592
|
+
|
|
1593
|
+
file ".env.production" do
|
|
1594
|
+
encrypted true
|
|
1595
|
+
env "STRIPE_SECRET_KEY", required: true
|
|
1596
|
+
env "PORT", min: 1024
|
|
1597
|
+
end
|
|
1588
1598
|
```
|
|
1589
1599
|
|
|
1590
|
-
|
|
1600
|
+
Both `dotenvx run -f .env.production -- node index.js` and `dotenvx validate -f .env.production` use the production rules. Block declarations inherit top-level options and override only the options they specify. A block's `encrypted` directive applies to all inherited and newly declared variables; a per-variable `encrypted:` option inside that block overrides it.
|
|
1601
|
+
|
|
1602
|
+
Paths in file blocks are relative to the Envfile. They match the selected paths exactly after path normalization (`./.env.production` matches `.env.production`); they are not basename matches or globs. Directory inputs and `DOTENV_PATH` use their resolved file paths.
|
|
1603
|
+
|
|
1604
|
+
When no file block matches, top-level rules apply. When one or more blocks match, each matching block's inherited rules must hold for the final resolved environment. Shell values, fallback files, and `--overload` cannot bypass them. A selected missing file still activates its block. Multiple matching blocks cannot cancel each other's restrictions; conflicting proxy domains are rejected. Blocks cannot be nested, and duplicate declarations within one scope or duplicate file blocks are errors.
|
|
1605
|
+
|
|
1591
1606
|
|
|
1592
1607
|
</details>
|
|
1593
1608
|
<details><summary>`run --strict`</summary><br>
|
|
@@ -2778,46 +2793,38 @@ $ dotenvx ls --json > dotenv-files.json
|
|
|
2778
2793
|
</details>
|
|
2779
2794
|
<details><summary>`validate`</summary><br>
|
|
2780
2795
|
|
|
2781
|
-
Validate `.env`
|
|
2796
|
+
Validate resolved `.env` values against an `Envfile` without running a command. An `Envfile` in the current directory is required; `.env.example` is not used for validation.
|
|
2782
2797
|
|
|
2783
|
-
```
|
|
2784
|
-
#
|
|
2785
|
-
DATABASE_URL
|
|
2786
|
-
|
|
2787
|
-
SENTRY_DSN
|
|
2798
|
+
```ruby
|
|
2799
|
+
# Envfile
|
|
2800
|
+
env "DATABASE_URL", type: "url"
|
|
2801
|
+
env "PORT", type: "port"
|
|
2802
|
+
env "SENTRY_DSN", optional: true
|
|
2788
2803
|
```
|
|
2789
2804
|
|
|
2790
2805
|
```sh
|
|
2791
2806
|
$ dotenvx validate
|
|
2792
|
-
[
|
|
2793
|
-
```
|
|
2794
|
-
|
|
2795
|
-
Use `-f` and `-fk` to validate a specific env file and keys file. The command exits with code `1` when validation fails and prints errors to stderr. On success, it exits with code `0`.
|
|
2807
|
+
[INVALID_ENV] DATABASE_URL is required; PORT is required
|
|
2796
2808
|
|
|
2797
|
-
```sh
|
|
2798
2809
|
$ dotenvx validate -f .env.production -fk .env.keys
|
|
2799
2810
|
```
|
|
2800
2811
|
|
|
2801
|
-
|
|
2802
|
-
<details><summary>`validate --ignore`</summary><br>
|
|
2812
|
+
The command enforces required values, types, enums, bounds, and encryption requirements. It exits with code `1` on validation or other loading errors. Missing env files are reported but do not fail validation when the resolved values satisfy Envfile; use `--strict` to make missing files fatal too. On success, it prints `▣ valid (.env)` (listing the loaded input files) and exits with code `0` on success. It does not change your shell's environment.
|
|
2803
2813
|
|
|
2804
|
-
|
|
2814
|
+
A missing Envfile reports `ENVFILE_REQUIRED`; invalid syntax reports `MALFORMED_ENVFILE`.
|
|
2805
2815
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
```
|
|
2816
|
+
</details>
|
|
2817
|
+
<details><summary>`validate --ignore`</summary><br>
|
|
2809
2818
|
|
|
2810
|
-
Ignore
|
|
2819
|
+
Ignore specific loading or value-validation errors:
|
|
2811
2820
|
|
|
2812
2821
|
```sh
|
|
2813
|
-
$ dotenvx validate --ignore=MISSING_ENV_FILE
|
|
2822
|
+
$ dotenvx validate --ignore=MISSING_ENV_FILE
|
|
2823
|
+
$ dotenvx validate --ignore=MISSING_ENV_FILE INVALID_ENV
|
|
2824
|
+
$ DOTENV_IGNORE=MISSING_ENV_FILE dotenvx validate
|
|
2814
2825
|
```
|
|
2815
2826
|
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
```sh
|
|
2819
|
-
$ DOTENV_IGNORE=MISSING_ENV_FILE,OTHER dotenvx validate
|
|
2820
|
-
```
|
|
2827
|
+
An Envfile is still required, even when errors are ignored.
|
|
2821
2828
|
|
|
2822
2829
|
</details>
|
|
2823
2830
|
<details><summary>`genexample`</summary><br>
|
|
@@ -3784,7 +3791,7 @@ INVALID_PUBLIC_KEY= # a public key is malformed or otherwise invalid
|
|
|
3784
3791
|
MALFORMED_ENCRYPTED_DATA= # the encrypted value is malformed
|
|
3785
3792
|
MISPAIRED_PRIVATE_KEY= # a private key does not match the existing public key
|
|
3786
3793
|
MISSING_DIRECTORY= # the requested directory does not exist
|
|
3787
|
-
|
|
3794
|
+
ENVFILE_REQUIRED= # the required Envfile does not exist
|
|
3788
3795
|
MISSING_ENV_FILE= # a requested environment file does not exist
|
|
3789
3796
|
MISSING_ENV_FILES= # no .env* files were found
|
|
3790
3797
|
MISSING_ENV_KEYS_FILE= # the requested .env.keys file does not exist
|
|
@@ -3792,11 +3799,12 @@ MISSING_KEY= # a requested environment key does not exist
|
|
|
3792
3799
|
MISSING_LOG_LEVEL= # the requested log level is not supported
|
|
3793
3800
|
MISSING_PRIVATE_KEY= # the private key required to decrypt a value is missing
|
|
3794
3801
|
MISSING_PUBLIC_KEY= # the public key required to encrypt a value is missing
|
|
3795
|
-
MISSING_REQUIRED= # validation detail for which required variables are missing; surfaced by
|
|
3802
|
+
MISSING_REQUIRED= # validation detail for which required variables are missing; surfaced by INVALID_ENV
|
|
3796
3803
|
MISSING_VALUE= # no value was supplied for a key
|
|
3797
3804
|
NOT_FOUND= # a private key was not found in the native secret store
|
|
3798
3805
|
PRECOMMIT_HOOK_MODIFY_FAILED= # dotenvx could not update the pre-commit hook
|
|
3799
|
-
|
|
3806
|
+
INVALID_ENV= # resolved values do not satisfy Envfile rules
|
|
3807
|
+
MALFORMED_ENVFILE= # Envfile syntax or configuration is invalid
|
|
3800
3808
|
WRONG_PRIVATE_KEY= # the supplied private key cannot decrypt the value
|
|
3801
3809
|
```
|
|
3802
3810
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.27.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a secure dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -49,18 +49,23 @@
|
|
|
49
49
|
"testshell": "shellspec --shell /bin/bash",
|
|
50
50
|
"release:check": "npm test && npm run testshell",
|
|
51
51
|
"prerelease": "npm run release:check",
|
|
52
|
-
"release": "npm run release:check && standard-version"
|
|
52
|
+
"release": "npm run release:check && standard-version",
|
|
53
|
+
"build:envfile": "peggy --format commonjs -o src/lib/helpers/envfileParser.js src/lib/grammars/envfile.peggy"
|
|
53
54
|
},
|
|
54
55
|
"funding": "https://dotenvx.com",
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"@dotenvx/primitives": "^3.0.2",
|
|
57
58
|
"@dotenvx/tooling": "^1.0.3",
|
|
59
|
+
"global-agent": "^3.0.0",
|
|
60
|
+
"node-forge": "^1.4.0",
|
|
61
|
+
"undici": "^6.28.1",
|
|
58
62
|
"yocto-spinner": "^1.2.1"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
65
|
"@yao-pkg/pkg": "^6.12.0",
|
|
62
66
|
"capture-console": "^1.0.2",
|
|
63
67
|
"esbuild": "^0.28.1",
|
|
68
|
+
"peggy": "^5.1.0",
|
|
64
69
|
"proxyquire": "^2.1.3",
|
|
65
70
|
"sinon": "^14.0.1",
|
|
66
71
|
"standard": "^17.1.2",
|
|
@@ -74,5 +79,10 @@
|
|
|
74
79
|
"publishConfig": {
|
|
75
80
|
"access": "public",
|
|
76
81
|
"provenance": true
|
|
82
|
+
},
|
|
83
|
+
"standard": {
|
|
84
|
+
"ignore": [
|
|
85
|
+
"src/lib/helpers/envfileParser.js"
|
|
86
|
+
]
|
|
77
87
|
}
|
|
78
88
|
}
|
|
@@ -18,7 +18,7 @@ async function decrypt () {
|
|
|
18
18
|
const envs = this.envs
|
|
19
19
|
const sesh = new Session()
|
|
20
20
|
const noArmor = options.armor === false || (await sesh.noArmor())
|
|
21
|
-
const
|
|
21
|
+
const noNative = options.native === false || options.noNative === true
|
|
22
22
|
|
|
23
23
|
let errorCount = 0
|
|
24
24
|
|
|
@@ -32,7 +32,7 @@ async function decrypt () {
|
|
|
32
32
|
ek: options.excludeKey,
|
|
33
33
|
fk: options.envKeysFile,
|
|
34
34
|
noArmor,
|
|
35
|
-
|
|
35
|
+
noNative,
|
|
36
36
|
onStatus: (text) => {
|
|
37
37
|
if (spinner && text) {
|
|
38
38
|
spinner.text = text
|
|
@@ -76,7 +76,7 @@ async function decrypt () {
|
|
|
76
76
|
ek: options.excludeKey,
|
|
77
77
|
fk: options.envKeysFile,
|
|
78
78
|
noArmor,
|
|
79
|
-
|
|
79
|
+
noNative,
|
|
80
80
|
onStatus: (text) => {
|
|
81
81
|
if (spinner && text) {
|
|
82
82
|
spinner.text = text
|
|
@@ -21,7 +21,7 @@ async function encryptAction () {
|
|
|
21
21
|
const fk = options.envKeysFile || '.env.keys'
|
|
22
22
|
const noCreate = options.create === false
|
|
23
23
|
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
24
|
-
const
|
|
24
|
+
const noNative = options.native === false || options.noNative === true
|
|
25
25
|
|
|
26
26
|
const noBitwarden = options.bitwarden === false || options.noBitwarden === true
|
|
27
27
|
const no1Password = options['1password'] === false || options.no1Password === true
|
|
@@ -30,7 +30,7 @@ async function encryptAction () {
|
|
|
30
30
|
|
|
31
31
|
// stdout - should not have a try so that exit codes can surface to stdout
|
|
32
32
|
if (options.stdout) {
|
|
33
|
-
const { processedEnvs } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate,
|
|
33
|
+
const { processedEnvs } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate, noNative, no1Password, noBitwarden })
|
|
34
34
|
|
|
35
35
|
if (spinner) spinner.stop()
|
|
36
36
|
for (const processedEnv of processedEnvs) {
|
|
@@ -51,7 +51,7 @@ async function encryptAction () {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
try {
|
|
54
|
-
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate,
|
|
54
|
+
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate, noNative, no1Password, noBitwarden })
|
|
55
55
|
|
|
56
56
|
if (keysSrc) {
|
|
57
57
|
await fsx.writeFileX(fk, keysSrc)
|
package/src/cli/actions/get.js
CHANGED
|
@@ -32,7 +32,7 @@ async function get (key) {
|
|
|
32
32
|
try {
|
|
33
33
|
const sesh = new Session()
|
|
34
34
|
const noArmor = options.armor === false || (await sesh.noArmor())
|
|
35
|
-
const
|
|
35
|
+
const noNative = options.native === false || options.noNative === true
|
|
36
36
|
const { parsed: resolved, errors } = await getResolver({
|
|
37
37
|
key,
|
|
38
38
|
envs,
|
|
@@ -40,7 +40,7 @@ async function get (key) {
|
|
|
40
40
|
all: options.all,
|
|
41
41
|
envKeysFile: resolveEnvKeysFile(options.envKeysFile),
|
|
42
42
|
noArmor,
|
|
43
|
-
|
|
43
|
+
noNative,
|
|
44
44
|
no1Password: options['1password'] === false || options.no1Password === true,
|
|
45
45
|
noBitwarden: options.bitwarden === false || options.noBitwarden === true,
|
|
46
46
|
onStatus: (text) => {
|
|
@@ -21,7 +21,7 @@ async function keypair (key) {
|
|
|
21
21
|
envFile: options.envFile,
|
|
22
22
|
envKeysFile: options.envKeysFile,
|
|
23
23
|
armor: options.armor,
|
|
24
|
-
|
|
24
|
+
noNative: options.native === false || options.noNative === true,
|
|
25
25
|
noBitwarden: options.bitwarden === false || options.noBitwarden === true,
|
|
26
26
|
no1Password: options['1password'] === false || options.no1Password === true,
|
|
27
27
|
token: options.token,
|
package/src/cli/actions/run.js
CHANGED
|
@@ -2,24 +2,18 @@ const path = require('path')
|
|
|
2
2
|
const { logger } = require('./../../shared/logger')
|
|
3
3
|
|
|
4
4
|
const executeCommand = require('./../../lib/helpers/executeCommand')
|
|
5
|
-
const envsResolver = require('./../../lib/resolvers/envs')
|
|
6
5
|
const catchAndLog = require('./../../lib/helpers/catchAndLog')
|
|
7
6
|
const createSpinner = require('../../lib/helpers/createSpinner')
|
|
8
|
-
const
|
|
7
|
+
const prepareValidatedEnv = require('../../lib/services/validate')
|
|
9
8
|
const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
|
|
10
9
|
const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
|
|
11
10
|
const normalizeDotenvConfigIgnore = require('../../lib/helpers/normalizeDotenvConfigIgnore')
|
|
12
|
-
const normalizeDotenvConfigPath = require('../../lib/helpers/normalizeDotenvConfigPath')
|
|
13
|
-
const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
|
|
14
|
-
const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
|
|
15
11
|
const mask = require('../../lib/helpers/mask')
|
|
16
12
|
const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
|
|
17
13
|
const maskProcessedEnvs = require('../../lib/helpers/maskProcessedEnvs')
|
|
18
14
|
const redactedValues = require('../../lib/helpers/redactedValues')
|
|
19
15
|
const { redactOutput } = require('../../lib/helpers/redactOutput')
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const { determine } = require('./../../lib/helpers/envResolution')
|
|
16
|
+
const configureProxy = require('../../lib/proxy/configureProxy')
|
|
23
17
|
|
|
24
18
|
function inferCommandArgsFromProcessArgv (argv) {
|
|
25
19
|
const runIndex = argv.indexOf('run')
|
|
@@ -65,6 +59,7 @@ async function run () {
|
|
|
65
59
|
}
|
|
66
60
|
let commandEnv = process.env
|
|
67
61
|
let sensitiveValues = []
|
|
62
|
+
let closeProxy
|
|
68
63
|
|
|
69
64
|
let commandArgs = this.args
|
|
70
65
|
if (commandArgs.length < 1) {
|
|
@@ -87,10 +82,6 @@ async function run () {
|
|
|
87
82
|
|
|
88
83
|
const ignore = options.ignore || []
|
|
89
84
|
|
|
90
|
-
const sesh = new Session()
|
|
91
|
-
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
92
|
-
const noKeychain = options.native === false || options.noNative === true
|
|
93
|
-
|
|
94
85
|
if (commandArgs.length < 1) {
|
|
95
86
|
if (spinner) spinner.stop()
|
|
96
87
|
|
|
@@ -107,29 +98,26 @@ async function run () {
|
|
|
107
98
|
}
|
|
108
99
|
|
|
109
100
|
try {
|
|
110
|
-
let envs = buildCommandEnvs(normalizeDotenvConfigPath(this.envs), options.convention)
|
|
111
|
-
envs = determine(envs, process.env)
|
|
112
|
-
|
|
113
101
|
const {
|
|
114
102
|
processedEnvs,
|
|
115
|
-
readableFilepaths
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
readableFilepaths,
|
|
104
|
+
hasEnvfile,
|
|
105
|
+
proxyCredentials,
|
|
106
|
+
proxyToken,
|
|
107
|
+
session: sesh,
|
|
108
|
+
proxyError,
|
|
109
|
+
validationError: error
|
|
110
|
+
} = await prepareValidatedEnv({
|
|
111
|
+
envs: this.envs,
|
|
112
|
+
options,
|
|
119
113
|
processEnv: process.env,
|
|
120
|
-
|
|
121
|
-
noArmor,
|
|
122
|
-
noKeychain,
|
|
123
|
-
no1Password: options['1password'] === false || options.no1Password === true,
|
|
124
|
-
noBitwarden: options.bitwarden === false || options.noBitwarden === true,
|
|
125
|
-
token: options.token,
|
|
114
|
+
requireEnvfile: false,
|
|
126
115
|
command: commandArgs,
|
|
127
116
|
onStatus: (text) => {
|
|
128
|
-
if (spinner && text)
|
|
129
|
-
spinner.text = text
|
|
130
|
-
}
|
|
117
|
+
if (spinner && text) spinner.text = text
|
|
131
118
|
}
|
|
132
119
|
})
|
|
120
|
+
if (proxyError) throw proxyError
|
|
133
121
|
|
|
134
122
|
if (redactEnabled) {
|
|
135
123
|
sensitiveValues = redactedValues(processedEnvs)
|
|
@@ -140,17 +128,13 @@ async function run () {
|
|
|
140
128
|
maskProcessedEnvs(processedEnvs, commandEnv, showChar)
|
|
141
129
|
}
|
|
142
130
|
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw error
|
|
151
|
-
} else {
|
|
152
|
-
logger.error(error.messageWithHelp || error.message)
|
|
153
|
-
}
|
|
131
|
+
if (error) {
|
|
132
|
+
if (ignore.includes(error.code)) {
|
|
133
|
+
logger.verbose(`ignored: ${error.message}`)
|
|
134
|
+
} else if (options.strict || hasEnvfile) {
|
|
135
|
+
throw error
|
|
136
|
+
} else {
|
|
137
|
+
logger.error(error.messageWithHelp || error.message)
|
|
154
138
|
}
|
|
155
139
|
}
|
|
156
140
|
|
|
@@ -198,7 +182,21 @@ async function run () {
|
|
|
198
182
|
}
|
|
199
183
|
}
|
|
200
184
|
|
|
201
|
-
|
|
185
|
+
const proxy = await configureProxy(commandArgs, commandEnv, proxyCredentials, sesh, proxyToken)
|
|
186
|
+
closeProxy = proxy.close
|
|
187
|
+
commandArgs = proxy.commandArgs
|
|
188
|
+
commandEnv = proxy.env
|
|
189
|
+
|
|
190
|
+
const gatedKeys = new Set((proxyCredentials || [])
|
|
191
|
+
.filter(credential => commandEnv[credential.name] === credential.placeholder)
|
|
192
|
+
.map(credential => credential.name))
|
|
193
|
+
const injectedKeys = uniqueInjectedKeys(processedEnvs)
|
|
194
|
+
for (const key of gatedKeys) {
|
|
195
|
+
injectedKeys.delete(key)
|
|
196
|
+
logger.verbose(`${key} proxied via Armor proxy`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let msg = ''
|
|
202
200
|
const envStringCount = processedEnvs.filter((processedEnv) => processedEnv.type === 'env' && processedEnv.parsed).length
|
|
203
201
|
if (readableFilepaths.length > 0 && envStringCount > 0) {
|
|
204
202
|
msg += ` from ${readableFilepaths.join(', ')}, and --env flag${envStringCount > 1 ? 's' : ''}`
|
|
@@ -209,8 +207,10 @@ async function run () {
|
|
|
209
207
|
}
|
|
210
208
|
|
|
211
209
|
if (spinner) spinner.stop()
|
|
212
|
-
logger.success(
|
|
210
|
+
if (gatedKeys.size > 0) logger.success(`⧈ proxied (${gatedKeys.size})${msg}`)
|
|
211
|
+
logger.success(`⟐ injected env (${injectedKeys.size})${msg}`)
|
|
213
212
|
} catch (error) {
|
|
213
|
+
if (closeProxy) await closeProxy()
|
|
214
214
|
if (spinner) spinner.stop()
|
|
215
215
|
if (error.code === 'PROMPT_CANCELLED') {
|
|
216
216
|
process.exit(130)
|
|
@@ -220,7 +220,11 @@ async function run () {
|
|
|
220
220
|
process.exit(1)
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
try {
|
|
224
|
+
await executeCommand(commandArgs, commandEnv, sensitiveValues, closeProxy)
|
|
225
|
+
} finally {
|
|
226
|
+
if (closeProxy) await closeProxy()
|
|
227
|
+
}
|
|
224
228
|
}
|
|
225
229
|
|
|
226
230
|
module.exports = run
|
package/src/cli/actions/set.js
CHANGED
|
@@ -59,7 +59,7 @@ async function set (key, value) {
|
|
|
59
59
|
const fk = options.envKeysFile || '.env.keys'
|
|
60
60
|
const noCreate = options.create === false
|
|
61
61
|
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
62
|
-
const
|
|
62
|
+
const noNative = options.native === false || options.noNative === true
|
|
63
63
|
|
|
64
64
|
const noBitwarden = options.bitwarden === false || options.noBitwarden === true
|
|
65
65
|
const no1Password = options['1password'] === false || options.no1Password === true
|
|
@@ -67,7 +67,7 @@ async function set (key, value) {
|
|
|
67
67
|
let errorCount = 0
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await setTransform({ envs, key, value, fk, noArmor, noCreate, encrypt,
|
|
70
|
+
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await setTransform({ envs, key, value, fk, noArmor, noCreate, encrypt, noNative, no1Password, noBitwarden })
|
|
71
71
|
|
|
72
72
|
if (keysSrc) {
|
|
73
73
|
await fsx.writeFileX(fk, keysSrc)
|
|
@@ -1,53 +1,35 @@
|
|
|
1
1
|
const { logger } = require('./../../shared/logger')
|
|
2
2
|
|
|
3
|
-
const envsResolver = require('./../../lib/resolvers/envs')
|
|
4
3
|
const catchAndLog = require('./../../lib/helpers/catchAndLog')
|
|
5
4
|
const createSpinner = require('../../lib/helpers/createSpinner')
|
|
6
|
-
const
|
|
5
|
+
const prepareValidatedEnv = require('../../lib/services/validate')
|
|
7
6
|
const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
|
|
8
7
|
const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
|
|
9
8
|
const normalizeDotenvConfigIgnore = require('../../lib/helpers/normalizeDotenvConfigIgnore')
|
|
10
|
-
const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
|
|
11
|
-
const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
|
|
12
|
-
const validateEnvExample = require('../../lib/helpers/validateEnvExample')
|
|
13
|
-
|
|
14
|
-
const { determine } = require('./../../lib/helpers/envResolution')
|
|
15
9
|
|
|
16
10
|
async function validate () {
|
|
17
11
|
const options = normalizeDotenvConfigIgnore(normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts())))
|
|
18
12
|
const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
|
|
19
13
|
const spinner = await createSpinner({ ...spinnerOptions, ...options, text: 'validating' })
|
|
20
14
|
const ignore = options.ignore || []
|
|
21
|
-
const validateEnv = { ...process.env }
|
|
22
15
|
let errorCount = 0
|
|
23
16
|
|
|
24
17
|
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
25
18
|
|
|
26
19
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const sesh = new Session()
|
|
31
|
-
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
32
|
-
const noKeychain = options.native === false || options.noNative === true
|
|
33
|
-
|
|
34
|
-
const { processedEnvs } = await envsResolver({
|
|
35
|
-
envs,
|
|
36
|
-
overload: options.overload,
|
|
37
|
-
processEnv: validateEnv,
|
|
38
|
-
envKeysFile: resolveEnvKeysFile(options.envKeysFile),
|
|
39
|
-
noArmor,
|
|
40
|
-
noKeychain,
|
|
41
|
-
no1Password: options['1password'] === false || options.no1Password === true,
|
|
42
|
-
noBitwarden: options.bitwarden === false || options.noBitwarden === true,
|
|
43
|
-
token: options.token,
|
|
20
|
+
const { processedEnvs, readableFilepaths, proxyError, validationError } = await prepareValidatedEnv({
|
|
21
|
+
envs: this.envs,
|
|
22
|
+
options,
|
|
44
23
|
onStatus: (text) => {
|
|
45
|
-
if (spinner && text)
|
|
46
|
-
spinner.text = text
|
|
47
|
-
}
|
|
24
|
+
if (spinner && text) spinner.text = text
|
|
48
25
|
}
|
|
49
26
|
})
|
|
50
27
|
|
|
28
|
+
const sources = [...readableFilepaths]
|
|
29
|
+
if (processedEnvs.some(env => env.type === 'env' && env.parsed)) sources.push('--env')
|
|
30
|
+
if (sources.length === 0) sources.push('shell environment')
|
|
31
|
+
const sourceSummary = `(${sources.join(', ')})`
|
|
32
|
+
|
|
51
33
|
for (const processedEnv of processedEnvs) {
|
|
52
34
|
for (const error of processedEnv.errors || []) {
|
|
53
35
|
if (ignore.includes(error.code)) {
|
|
@@ -55,18 +37,24 @@ async function validate () {
|
|
|
55
37
|
continue
|
|
56
38
|
}
|
|
57
39
|
|
|
58
|
-
errorCount += 1
|
|
40
|
+
if (error.code !== 'MISSING_ENV_FILE' || options.strict) errorCount += 1
|
|
41
|
+
if (error.code === 'MISSING_ENV_FILE' && options.convention && !options.strict) continue
|
|
59
42
|
logger.error(error.messageWithHelp || error.message)
|
|
60
43
|
}
|
|
61
44
|
}
|
|
62
45
|
|
|
63
|
-
|
|
46
|
+
if (proxyError) {
|
|
47
|
+
proxyError.message += ` ${sourceSummary}`
|
|
48
|
+
throw proxyError
|
|
49
|
+
}
|
|
50
|
+
|
|
64
51
|
if (validationError) {
|
|
52
|
+
const message = `${validationError.message} ${sourceSummary}`
|
|
65
53
|
if (ignore.includes(validationError.code)) {
|
|
66
|
-
logger.verbose(`ignored: ${
|
|
54
|
+
logger.verbose(`ignored: ${message}`)
|
|
67
55
|
} else {
|
|
68
56
|
errorCount += 1
|
|
69
|
-
logger.error(
|
|
57
|
+
logger.error(message)
|
|
70
58
|
}
|
|
71
59
|
}
|
|
72
60
|
|
|
@@ -75,7 +63,7 @@ async function validate () {
|
|
|
75
63
|
if (errorCount > 0) {
|
|
76
64
|
process.exit(1)
|
|
77
65
|
} else {
|
|
78
|
-
logger.success(
|
|
66
|
+
logger.success(`▣ valid ${sourceSummary}`)
|
|
79
67
|
}
|
|
80
68
|
} catch (error) {
|
|
81
69
|
if (spinner) spinner.stop()
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -73,7 +73,6 @@ program.command('run')
|
|
|
73
73
|
.option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
|
|
74
74
|
.option('--redact', 'redact injected values except keys ending in _PLAIN', false)
|
|
75
75
|
.option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
|
|
76
|
-
.option('--validate', 'validate against .env.example', false)
|
|
77
76
|
.option('--strict', 'process.exit(1) on any errors', false)
|
|
78
77
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
79
78
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
@@ -224,7 +223,8 @@ program.command('ls')
|
|
|
224
223
|
|
|
225
224
|
// dotenvx validate
|
|
226
225
|
program.command('validate')
|
|
227
|
-
.description('validate .env file(s) against
|
|
226
|
+
.description('validate .env file(s) against Envfile')
|
|
227
|
+
.option('--strict', 'process.exit(1) on any errors, including missing env files', false)
|
|
228
228
|
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
|
|
229
229
|
.option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
|
|
230
230
|
.option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
|