@dotenvx/dotenvx 1.57.5 → 1.59.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 +15 -1
- package/README.md +6 -6
- package/package.json +1 -1
- package/src/cli/actions/encrypt.js +3 -2
- package/src/cli/actions/set.js +2 -1
- package/src/cli/dotenvx.js +2 -0
- package/src/lib/helpers/kits/sample.js +33 -0
- package/src/lib/services/encrypt.js +12 -1
- package/src/lib/services/sets.js +29 -2
- package/src/shared/logger.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.59.0...main)
|
|
6
|
+
|
|
7
|
+
## [1.59.0](https://github.com/dotenvx/dotenvx/compare/v1.58.0...v1.59.0) (2026-03-28)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* `encrypt` and `set` now create a `.env` file if one does not exist ([#771](https://github.com/dotenvx/dotenvx/pull/771))
|
|
12
|
+
* pass `--no-create` to prevent file creation
|
|
13
|
+
|
|
14
|
+
## [1.58.0](https://github.com/dotenvx/dotenvx/compare/v1.57.5...v1.58.0) (2026-03-27)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
* Changed runtime injection message to format `⟐ injecting env (N) from FILE · dotenvx@VERSION` ([#770](https://github.com/dotenvx/dotenvx/pull/770))
|
|
6
19
|
|
|
7
20
|
## [1.57.5](https://github.com/dotenvx/dotenvx/compare/v1.57.4...v1.57.5) (2026-03-26)
|
|
8
21
|
|
|
@@ -35,6 +48,7 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
35
48
|
* improved error logs and compacted most to a single line ([#755](https://github.com/dotenvx/dotenvx/pull/755))
|
|
36
49
|
* introduced leading log glyphs as a visual status language:
|
|
37
50
|
|
|
51
|
+
* `⟐` success action (injected)
|
|
38
52
|
* `◈` success action (encrypted)
|
|
39
53
|
* `◇` success action (set plain value, decrypted)
|
|
40
54
|
* `⟳` success action (rotated)
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ or install globally - *unlocks dotenv for any language, framework, or platform!*
|
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
36
|
npm i -g @dotenvx/dotenvx
|
|
37
|
-
dotenvx
|
|
37
|
+
dotenvx encrypt
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
[](https://npmjs.com/@dotenvx/dotenvx)
|
|
@@ -46,7 +46,7 @@ dotenvx help
|
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
48
|
curl -sfS https://dotenvx.sh | sh
|
|
49
|
-
dotenvx
|
|
49
|
+
dotenvx encrypt
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
[](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
|
|
@@ -59,7 +59,7 @@ dotenvx help
|
|
|
59
59
|
|
|
60
60
|
```sh
|
|
61
61
|
brew install dotenvx/brew/dotenvx
|
|
62
|
-
dotenvx
|
|
62
|
+
dotenvx encrypt
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
[](https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb)
|
|
@@ -71,7 +71,7 @@ dotenvx help
|
|
|
71
71
|
<details><summary>with docker 🐳</summary><br>
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
docker run -it --rm -v $(pwd):/app dotenv/dotenvx
|
|
74
|
+
docker run -it --rm -v $(pwd):/app dotenv/dotenvx encrypt
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
[](https://hub.docker.com/r/dotenv/dotenvx)
|
|
@@ -85,7 +85,7 @@ docker run -it --rm -v $(pwd):/app dotenv/dotenvx help
|
|
|
85
85
|
```sh
|
|
86
86
|
curl -L -o dotenvx.tar.gz "https://github.com/dotenvx/dotenvx/releases/latest/download/dotenvx-$(uname -s)-$(uname -m).tar.gz"
|
|
87
87
|
tar -xzf dotenvx.tar.gz
|
|
88
|
-
./dotenvx
|
|
88
|
+
./dotenvx encrypt
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
[](https://github.com/dotenvx/dotenvx/releases)
|
|
@@ -99,7 +99,7 @@ tar -xzf dotenvx.tar.gz
|
|
|
99
99
|
|
|
100
100
|
```sh
|
|
101
101
|
winget install dotenvx
|
|
102
|
-
dotenvx
|
|
102
|
+
dotenvx encrypt
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
</details>
|
package/package.json
CHANGED
|
@@ -12,12 +12,13 @@ function encrypt () {
|
|
|
12
12
|
|
|
13
13
|
const envs = this.envs
|
|
14
14
|
const opsOn = options.opsOff !== true
|
|
15
|
+
const noCreate = options.create === false
|
|
15
16
|
|
|
16
17
|
// stdout - should not have a try so that exit codes can surface to stdout
|
|
17
18
|
if (options.stdout) {
|
|
18
19
|
const {
|
|
19
20
|
processedEnvs
|
|
20
|
-
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
|
|
21
|
+
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn, noCreate).run()
|
|
21
22
|
|
|
22
23
|
for (const processedEnv of processedEnvs) {
|
|
23
24
|
console.log(processedEnv.envSrc)
|
|
@@ -29,7 +30,7 @@ function encrypt () {
|
|
|
29
30
|
processedEnvs,
|
|
30
31
|
changedFilepaths,
|
|
31
32
|
unchangedFilepaths
|
|
32
|
-
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
|
|
33
|
+
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn, noCreate).run()
|
|
33
34
|
|
|
34
35
|
for (const processedEnv of processedEnvs) {
|
|
35
36
|
logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
|
package/src/cli/actions/set.js
CHANGED
|
@@ -23,12 +23,13 @@ function set (key, value) {
|
|
|
23
23
|
const envs = this.envs
|
|
24
24
|
const envKeysFilepath = options.envKeysFile
|
|
25
25
|
const opsOn = options.opsOff !== true
|
|
26
|
+
const noCreate = options.create === false
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
processedEnvs,
|
|
29
30
|
changedFilepaths,
|
|
30
31
|
unchangedFilepaths
|
|
31
|
-
} = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn).run()
|
|
32
|
+
} = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn, noCreate).run()
|
|
32
33
|
|
|
33
34
|
let withEncryption = ''
|
|
34
35
|
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -116,6 +116,7 @@ program.command('set')
|
|
|
116
116
|
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
|
|
117
117
|
.option('-c, --encrypt', 'encrypt value', true)
|
|
118
118
|
.option('-p, --plain', 'store value as plain text', false)
|
|
119
|
+
.option('--no-create', 'do not create .env file(s) when missing')
|
|
119
120
|
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
|
|
120
121
|
.action(function (...args) {
|
|
121
122
|
this.envs = envs
|
|
@@ -130,6 +131,7 @@ program.command('encrypt')
|
|
|
130
131
|
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
|
|
131
132
|
.option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
|
|
132
133
|
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
|
|
134
|
+
.option('--no-create', 'do not create .env file(s) when missing')
|
|
133
135
|
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
|
|
134
136
|
.option('--stdout', 'send to stdout')
|
|
135
137
|
.action(function (...args) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const SAMPLE_ENV_KIT = `
|
|
2
|
+
# ── Database ─────────────────────────────────────
|
|
3
|
+
DATABASE_URL="postgresql://postgres:pass@db.ref.supabase.co:5432/postgres"
|
|
4
|
+
|
|
5
|
+
# ── Auth ─────────────────────────────────────────
|
|
6
|
+
AUTH0_CLIENT_ID="xxxx"
|
|
7
|
+
AUTH0_CLIENT_SECRET="xxxx"
|
|
8
|
+
|
|
9
|
+
# ── AI / LLM ────────────────────────────────────
|
|
10
|
+
OPENAI_API_KEY="sk-xxxx"
|
|
11
|
+
ANTHROPIC_API_KEY="sk-ant-xxxx"
|
|
12
|
+
|
|
13
|
+
# ── Email ────────────────────────────────────────
|
|
14
|
+
RESEND_API_KEY="re_xxxx"
|
|
15
|
+
|
|
16
|
+
# ── Cloud Storage ────────────────────────────────
|
|
17
|
+
AWS_ACCESS_KEY_ID="xxxx"
|
|
18
|
+
AWS_SECRET_ACCESS_KEY="xxxx"
|
|
19
|
+
|
|
20
|
+
# ── Analytics / Monitoring ───────────────────────
|
|
21
|
+
SENTRY_DSN="https://hex@o1234.ingest.us.sentry.io/1234567"
|
|
22
|
+
|
|
23
|
+
# ── Payments ─────────────────────────────────────
|
|
24
|
+
STRIPE_API_KEY="sk_test_xxxx"
|
|
25
|
+
|
|
26
|
+
# ── Feature Flags ────────────────────────────────
|
|
27
|
+
FLAGSMITH_ENV_ID="xxxx"
|
|
28
|
+
|
|
29
|
+
# ── CI/CD / Deployment ──────────────────────────
|
|
30
|
+
VERCEL_TOKEN="vcp_xxxx"
|
|
31
|
+
`.trimStart()
|
|
32
|
+
|
|
33
|
+
module.exports = SAMPLE_ENV_KIT
|
|
@@ -26,14 +26,16 @@ const {
|
|
|
26
26
|
const replace = require('./../helpers/replace')
|
|
27
27
|
const dotenvParse = require('./../helpers/dotenvParse')
|
|
28
28
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
29
|
+
const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
|
|
29
30
|
|
|
30
31
|
class Encrypt {
|
|
31
|
-
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
|
|
32
|
+
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false, noCreate = false) {
|
|
32
33
|
this.envs = determine(envs, process.env)
|
|
33
34
|
this.key = key
|
|
34
35
|
this.excludeKey = excludeKey
|
|
35
36
|
this.envKeysFilepath = envKeysFilepath
|
|
36
37
|
this.opsOn = opsOn
|
|
38
|
+
this.noCreate = noCreate
|
|
37
39
|
|
|
38
40
|
this.processedEnvs = []
|
|
39
41
|
this.changedFilepaths = new Set()
|
|
@@ -75,8 +77,17 @@ class Encrypt {
|
|
|
75
77
|
row.envFilepath = envFilepath
|
|
76
78
|
|
|
77
79
|
try {
|
|
80
|
+
// if noCreate is on then detectEncoding will throw and we'll halt the calls
|
|
81
|
+
// but if noCreate is false then create the file if it doesn't exist
|
|
82
|
+
if (!fsx.existsSync(filepath) && !this.noCreate) {
|
|
83
|
+
fsx.writeFileX(filepath, SAMPLE_ENV_KIT)
|
|
84
|
+
}
|
|
78
85
|
const encoding = detectEncoding(filepath)
|
|
79
86
|
let envSrc = fsx.readFileX(filepath, { encoding })
|
|
87
|
+
if (envSrc.trim().length === 0) {
|
|
88
|
+
envSrc = SAMPLE_ENV_KIT
|
|
89
|
+
row.kitCreated = 'sample'
|
|
90
|
+
}
|
|
80
91
|
const envParsed = dotenvParse(envSrc)
|
|
81
92
|
|
|
82
93
|
let publicKey
|
package/src/lib/services/sets.js
CHANGED
|
@@ -27,13 +27,14 @@ const dotenvParse = require('./../helpers/dotenvParse')
|
|
|
27
27
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
28
28
|
|
|
29
29
|
class Sets {
|
|
30
|
-
constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false) {
|
|
30
|
+
constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false, noCreate = false) {
|
|
31
31
|
this.envs = determine(envs, process.env)
|
|
32
32
|
this.key = key
|
|
33
33
|
this.value = value
|
|
34
34
|
this.encrypt = encrypt
|
|
35
35
|
this.envKeysFilepath = envKeysFilepath
|
|
36
36
|
this.opsOn = opsOn
|
|
37
|
+
this.noCreate = noCreate
|
|
37
38
|
|
|
38
39
|
this.processedEnvs = []
|
|
39
40
|
this.changedFilepaths = new Set()
|
|
@@ -72,10 +73,30 @@ class Sets {
|
|
|
72
73
|
row.changed = false
|
|
73
74
|
|
|
74
75
|
try {
|
|
76
|
+
let seededWithInitialKey = false
|
|
77
|
+
|
|
78
|
+
if (!fsx.existsSync(filepath)) {
|
|
79
|
+
if (this.noCreate) {
|
|
80
|
+
detectEncoding(filepath) // throws ENOENT
|
|
81
|
+
} else {
|
|
82
|
+
fsx.writeFileX(filepath, '')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
const encoding = detectEncoding(filepath)
|
|
76
87
|
let envSrc = fsx.readFileX(filepath, { encoding })
|
|
88
|
+
|
|
89
|
+
// blank files seeded by `set` should contain only the key being set
|
|
90
|
+
if (row.key && envSrc.trim().length === 0) {
|
|
91
|
+
envSrc = `${row.key}="${this.value}"\n`
|
|
92
|
+
seededWithInitialKey = true
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
const envParsed = dotenvParse(envSrc)
|
|
78
96
|
row.originalValue = envParsed[row.key] || null
|
|
97
|
+
if (seededWithInitialKey) {
|
|
98
|
+
row.originalValue = null
|
|
99
|
+
}
|
|
79
100
|
const wasPlainText = !isEncrypted(row.originalValue)
|
|
80
101
|
this.readableFilepaths.add(envFilepath)
|
|
81
102
|
|
|
@@ -119,7 +140,13 @@ class Sets {
|
|
|
119
140
|
|
|
120
141
|
const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt
|
|
121
142
|
const valueChanged = this.value !== row.originalValue
|
|
122
|
-
|
|
143
|
+
const shouldPersistSeededPlainValue = seededWithInitialKey && !this.encrypt
|
|
144
|
+
|
|
145
|
+
if (shouldPersistSeededPlainValue) {
|
|
146
|
+
row.envSrc = envSrc
|
|
147
|
+
this.changedFilepaths.add(envFilepath)
|
|
148
|
+
row.changed = true
|
|
149
|
+
} else if (goingFromPlainTextToEncrypted || valueChanged) {
|
|
123
150
|
row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value)
|
|
124
151
|
this.changedFilepaths.add(envFilepath)
|
|
125
152
|
row.changed = true
|
package/src/shared/logger.js
CHANGED
|
@@ -17,7 +17,7 @@ const levels = {
|
|
|
17
17
|
const error = (m) => bold(getColor('red')(`☠ ${m}`))
|
|
18
18
|
const warn = (m) => getColor('orangered')(`⚠ ${m}`)
|
|
19
19
|
const success = getColor('amber')
|
|
20
|
-
const successv = getColor('amber')
|
|
20
|
+
const successv = (m) => getColor('amber')(`⟐ ${m}`)
|
|
21
21
|
const info = getColor('gray')
|
|
22
22
|
const help = getColor('dodgerblue')
|
|
23
23
|
const verbose = getColor('plum')
|
|
@@ -57,7 +57,7 @@ function formatMessage (level, message) {
|
|
|
57
57
|
case 'success':
|
|
58
58
|
return success(formattedMessage)
|
|
59
59
|
case 'successv': // success with 'version'
|
|
60
|
-
return successv(
|
|
60
|
+
return successv(`${formattedMessage} · ${currentName}@${currentVersion}`)
|
|
61
61
|
// info
|
|
62
62
|
case 'info':
|
|
63
63
|
return info(formattedMessage)
|