@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 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.19.1...main)
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.19.1",
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.1.1",
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.8",
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
@@ -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 to Dotenvx Armor [www.dotenvx.com/armor]')
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