@dotenvx/dotenvx 2.19.1 → 2.20.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 +11 -1
- package/README.md +59 -0
- package/package.json +3 -3
- package/src/cli/actions/del.js +56 -0
- package/src/cli/dotenvx.js +13 -1
- package/src/cli/help.js +12 -0
- package/src/lib/services/prebuild.js +2 -2
- package/src/lib/services/precommit.js +2 -2
- package/src/lib/transforms/del.js +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
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.20.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.20.0](https://github.com/dotenvx/dotenvx/compare/v2.19.1...v2.20.0) (2026-08-07)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add `dotenvx del` command for deleting a key from a .env file ([#939](https://github.com/dotenvx/dotenvx/pull/939))
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* Exempt `.env.vault` from `precommit`/`prebuild` sealed checks (vault-format ciphertext is already safe to commit)
|
|
6
16
|
|
|
7
17
|
## [2.19.1](https://github.com/dotenvx/dotenvx/compare/v2.19.0...v2.19.1) (2026-07-29)
|
|
8
18
|
|
package/README.md
CHANGED
|
@@ -130,6 +130,13 @@ More examples
|
|
|
130
130
|
|
|
131
131
|
Run Claude with your real secrets while redacting them from its output.
|
|
132
132
|
|
|
133
|
+
Prerequisite: install [Claude Code](https://code.claude.com/docs/en/setup) to get the `claude` command.
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
$ curl -fsSL https://claude.ai/install.sh | bash
|
|
137
|
+
$ claude --version
|
|
138
|
+
```
|
|
139
|
+
|
|
133
140
|
```sh
|
|
134
141
|
$ echo "HELLO=World" > .env
|
|
135
142
|
|
|
@@ -144,6 +151,13 @@ see [Claude redaction guide](https://dotenvx.com/docs/cli/run-redact-claude-prin
|
|
|
144
151
|
|
|
145
152
|
Run Codex with your real secrets while redacting them from its output.
|
|
146
153
|
|
|
154
|
+
Prerequisite: install the [Codex CLI](https://developers.openai.com/codex/cli/) to get the `codex` command.
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
$ npm install -g @openai/codex
|
|
158
|
+
$ codex --version
|
|
159
|
+
```
|
|
160
|
+
|
|
147
161
|
```sh
|
|
148
162
|
$ echo "HELLO=World" > .env
|
|
149
163
|
|
|
@@ -153,6 +167,27 @@ Hello [REDACTED]
|
|
|
153
167
|
|
|
154
168
|
see [Codex redaction guide](https://dotenvx.com/docs/cli/run-redact-codex-exec)
|
|
155
169
|
|
|
170
|
+
</details>
|
|
171
|
+
<details><summary>Cursor 🖱️</summary><br>
|
|
172
|
+
|
|
173
|
+
Run Cursor with your real secrets while redacting them from its output.
|
|
174
|
+
|
|
175
|
+
Prerequisite: install the separate [Cursor CLI](https://cursor.com/cli). Installing the Cursor desktop app does not necessarily install the `agent` command.
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
$ curl https://cursor.com/install -fsS | bash
|
|
179
|
+
$ agent --version
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
$ echo "HELLO=World" > .env
|
|
184
|
+
|
|
185
|
+
$ dotenvx run --redact -- agent -p --force 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --output-format text
|
|
186
|
+
Hello [REDACTED]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
see [Cursor redaction guide](https://dotenvx.com/docs/secrets-in-cursor)
|
|
190
|
+
|
|
156
191
|
</details>
|
|
157
192
|
<details><summary>1Password 🔐</summary><br>
|
|
158
193
|
|
|
@@ -2176,6 +2211,30 @@ Do not create a missing `.env` file.
|
|
|
2176
2211
|
$ dotenvx set HELLO World -f .env.production --no-create
|
|
2177
2212
|
```
|
|
2178
2213
|
|
|
2214
|
+
</details>
|
|
2215
|
+
<details><summary>`del KEY`</summary><br>
|
|
2216
|
+
|
|
2217
|
+
Delete a key from your `.env` file.
|
|
2218
|
+
|
|
2219
|
+
```sh
|
|
2220
|
+
$ echo "HELLO=World" > .env
|
|
2221
|
+
|
|
2222
|
+
$ dotenvx del HELLO
|
|
2223
|
+
◇ removed HELLO (.env)
|
|
2224
|
+
```
|
|
2225
|
+
|
|
2226
|
+
</details>
|
|
2227
|
+
<details><summary>`del KEY -f`</summary><br>
|
|
2228
|
+
|
|
2229
|
+
Delete a key from another `.env` file.
|
|
2230
|
+
|
|
2231
|
+
```sh
|
|
2232
|
+
$ echo "HELLO=production" > .env.production
|
|
2233
|
+
|
|
2234
|
+
$ dotenvx del HELLO -f .env.production
|
|
2235
|
+
◇ removed HELLO (.env.production)
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2179
2238
|
</details>
|
|
2180
2239
|
<details><summary>`encrypt`</summary><br>
|
|
2181
2240
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.20.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a secure dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"funding": "https://dotenvx.com",
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@dotenvx/primitives": "^2.
|
|
55
|
+
"@dotenvx/primitives": "^2.2.0",
|
|
56
56
|
"@dotenvx/tooling": "^1.0.3",
|
|
57
57
|
"yocto-spinner": "^1.2.1"
|
|
58
58
|
},
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"tap": "^21.7.4"
|
|
68
68
|
},
|
|
69
69
|
"overrides": {
|
|
70
|
-
"brace-expansion": "^5.0.
|
|
70
|
+
"brace-expansion": "^5.0.9",
|
|
71
71
|
"minimatch": "^10.2.5"
|
|
72
72
|
},
|
|
73
73
|
"publishConfig": {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fsx = require('./../../lib/helpers/fsx')
|
|
2
|
+
const { logger } = require('./../../shared/logger')
|
|
3
|
+
|
|
4
|
+
const delTransform = require('./../../lib/transforms/del')
|
|
5
|
+
|
|
6
|
+
const catchAndLog = require('../../lib/helpers/catchAndLog')
|
|
7
|
+
const createSpinner = require('../../lib/helpers/createSpinner')
|
|
8
|
+
|
|
9
|
+
async function del (key) {
|
|
10
|
+
const options = this.opts()
|
|
11
|
+
const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
|
|
12
|
+
const spinner = await createSpinner({ ...spinnerOptions, ...options, text: 'deleting' })
|
|
13
|
+
|
|
14
|
+
logger.debug(`key: ${key}`)
|
|
15
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
16
|
+
|
|
17
|
+
const envs = this.envs || []
|
|
18
|
+
|
|
19
|
+
let errorCount = 0
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { processedEnvs, changedFilepaths, unchangedFilepaths } = await delTransform({ envs, key })
|
|
23
|
+
|
|
24
|
+
if (spinner) spinner.stop()
|
|
25
|
+
for (const processedEnv of processedEnvs) {
|
|
26
|
+
logger.verbose(`deleting for ${processedEnv.envFilepath}`)
|
|
27
|
+
if (processedEnv.error) {
|
|
28
|
+
errorCount += 1
|
|
29
|
+
logger.error(processedEnv.error.messageWithHelp || processedEnv.error.message)
|
|
30
|
+
} else if (processedEnv.changed) {
|
|
31
|
+
await fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
|
|
32
|
+
logger.verbose(`${processedEnv.key} deleted (${processedEnv.envFilepath})`)
|
|
33
|
+
} else {
|
|
34
|
+
logger.verbose(`no change ${processedEnv.envFilepath} (${processedEnv.filepath})`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (changedFilepaths.length > 0) {
|
|
39
|
+
logger.success(`◇ removed ${key} (${changedFilepaths.join(',')})`)
|
|
40
|
+
} else if (unchangedFilepaths.length > 0) {
|
|
41
|
+
logger.info(`○ no change (${unchangedFilepaths})`)
|
|
42
|
+
} else {
|
|
43
|
+
// do nothing - scenario when no .env files found
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (errorCount > 0) {
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (spinner) spinner.stop()
|
|
51
|
+
catchAndLog(error)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = del
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -129,6 +129,18 @@ program.command('set')
|
|
|
129
129
|
return require('./actions/set').apply(this, args)
|
|
130
130
|
})
|
|
131
131
|
|
|
132
|
+
// dotenvx del
|
|
133
|
+
program.command('del')
|
|
134
|
+
.usage('<KEY> [options]')
|
|
135
|
+
.description('delete a single environment variable')
|
|
136
|
+
.addHelpText('after', help.del)
|
|
137
|
+
.argument('KEY', 'KEY')
|
|
138
|
+
.option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
|
|
139
|
+
.action(function (...args) {
|
|
140
|
+
this.envs = envs
|
|
141
|
+
return require('./actions/del').apply(this, args)
|
|
142
|
+
})
|
|
143
|
+
|
|
132
144
|
// dotenvx encrypt
|
|
133
145
|
program.command('encrypt')
|
|
134
146
|
.description('encrypt .env file(s)')
|
|
@@ -317,7 +329,7 @@ program.addHelpText('after', 'Professional Security: ')
|
|
|
317
329
|
program.addHelpText('after', ' lock ⊡ lock private keys with a local passphrase')
|
|
318
330
|
program.addHelpText('after', ' native ⌥ move private keys into your OS secret store')
|
|
319
331
|
program.addHelpText('after', ' armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
|
|
320
|
-
program.addHelpText('after', ' curl ⛨ call authenticated api
|
|
332
|
+
program.addHelpText('after', ' curl ⛨ call authenticated api Dotenvx Armor [www.dotenvx.com/armor]')
|
|
321
333
|
|
|
322
334
|
// dotenvx native
|
|
323
335
|
require('./commands/native')(program.command('native', { hidden: true }))
|
package/src/cli/help.js
CHANGED
|
@@ -90,6 +90,17 @@ Examples:
|
|
|
90
90
|
`
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
const del = function () {
|
|
94
|
+
return `
|
|
95
|
+
Examples:
|
|
96
|
+
|
|
97
|
+
\`\`\`
|
|
98
|
+
$ dotenvx del KEY
|
|
99
|
+
$ dotenvx del KEY -f .env.production
|
|
100
|
+
\`\`\`
|
|
101
|
+
`
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
const curl = function () {
|
|
94
105
|
return `
|
|
95
106
|
Endpoints:
|
|
@@ -130,5 +141,6 @@ module.exports = {
|
|
|
130
141
|
prebuild,
|
|
131
142
|
gitignore,
|
|
132
143
|
set,
|
|
144
|
+
del,
|
|
133
145
|
curl
|
|
134
146
|
}
|
|
@@ -43,7 +43,7 @@ class Prebuild {
|
|
|
43
43
|
|
|
44
44
|
// check if that file is being ignored
|
|
45
45
|
if (ig.ignores(file)) {
|
|
46
|
-
if (file === '.env.example' || file === '.env.x') {
|
|
46
|
+
if (file === '.env.example' || file === '.env.vault' || file === '.env.x') {
|
|
47
47
|
const warning = new Errors({
|
|
48
48
|
message: `${file} ignored (should not be)`,
|
|
49
49
|
help: `fix: [dotenvx gitignore --pattern !${file}]`
|
|
@@ -51,7 +51,7 @@ class Prebuild {
|
|
|
51
51
|
warnings.push(warning)
|
|
52
52
|
}
|
|
53
53
|
} else {
|
|
54
|
-
if (file !== '.env.example' && file !== '.env.x') {
|
|
54
|
+
if (file !== '.env.example' && file !== '.env.vault' && file !== '.env.x') {
|
|
55
55
|
const src = fsx.readFileXSync(file)
|
|
56
56
|
const encrypted = sealed(src)
|
|
57
57
|
|
|
@@ -60,7 +60,7 @@ class Precommit {
|
|
|
60
60
|
if (this._isFileToBeCommitted(file)) {
|
|
61
61
|
// check if that file is being ignored
|
|
62
62
|
if (ig.ignores(file)) {
|
|
63
|
-
if (filename === '.env.example' || filename === '.env.x') {
|
|
63
|
+
if (filename === '.env.example' || filename === '.env.vault' || filename === '.env.x') {
|
|
64
64
|
const warning = new Errors({
|
|
65
65
|
message: `${file} ignored (should not be)`,
|
|
66
66
|
help: `fix: [dotenvx gitignore --pattern !${file}]`
|
|
@@ -68,7 +68,7 @@ class Precommit {
|
|
|
68
68
|
warnings.push(warning)
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
71
|
-
if (filename !== '.env.example' && filename !== '.env.x') {
|
|
71
|
+
if (filename !== '.env.example' && filename !== '.env.vault' && filename !== '.env.x') {
|
|
72
72
|
const src = fsx.readFileXSync(file)
|
|
73
73
|
const encrypted = sealed(src)
|
|
74
74
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const fsx = require('./../helpers/fsx')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { remove } = require('@dotenvx/primitives')
|
|
4
|
+
|
|
5
|
+
const TYPE_ENV_FILE = 'envFile'
|
|
6
|
+
|
|
7
|
+
const Errors = require('./../helpers/errors')
|
|
8
|
+
const { determine } = require('./../helpers/envResolution')
|
|
9
|
+
const detectEncoding = require('./../helpers/detectEncoding')
|
|
10
|
+
|
|
11
|
+
async function delTransform (options = {}) {
|
|
12
|
+
const envs = options.envs || []
|
|
13
|
+
const key = options.key
|
|
14
|
+
|
|
15
|
+
const processedEnvs = []
|
|
16
|
+
const changedFilepaths = []
|
|
17
|
+
const unchangedFilepaths = []
|
|
18
|
+
|
|
19
|
+
for (const env of determine(envs, process.env)) {
|
|
20
|
+
if (env.type !== TYPE_ENV_FILE) {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const envFilepath = env.envFilepath || env.value
|
|
25
|
+
const filepath = env.filepath || path.resolve(envFilepath)
|
|
26
|
+
const row = { key, type: TYPE_ENV_FILE, filepath, envFilepath, changed: false }
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const encoding = await detectEncoding(filepath)
|
|
30
|
+
const before = await fsx.readFileX(filepath, { encoding })
|
|
31
|
+
const after = remove(before, key)
|
|
32
|
+
|
|
33
|
+
row.envSrc = after
|
|
34
|
+
if (after !== before) {
|
|
35
|
+
row.changed = true
|
|
36
|
+
changedFilepaths.push(envFilepath)
|
|
37
|
+
} else {
|
|
38
|
+
unchangedFilepaths.push(envFilepath)
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT') {
|
|
42
|
+
row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
|
|
43
|
+
} else {
|
|
44
|
+
row.error = error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
processedEnvs.push(row)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
processedEnvs,
|
|
53
|
+
changedFilepaths,
|
|
54
|
+
unchangedFilepaths
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = delTransform
|