@dotenvx/dotenvx 2.0.0 → 2.1.1

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,28 @@
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.0.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.1.1...main)
6
+
7
+ ## [2.1.1](https://github.com/dotenvx/dotenvx/compare/v2.1.0...v2.1.1) (2026-07-02)
8
+
9
+ ### Changed
10
+
11
+ * Support `DOTENV_CONFIG_QUIET=true` as synonym for `--quiet` flag ([#866](https://github.com/dotenvx/dotenvx/pull/866))
12
+
13
+ ## [2.1.0](https://github.com/dotenvx/dotenvx/compare/v2.0.0...v2.1.0) (2026-07-02)
14
+
15
+ ### Added
16
+
17
+ * Add interactive input mode for `set` ([#862](https://github.com/dotenvx/dotenvx/pull/862))
18
+ * Support leaving `_PLAIN` suffixed keys unencrypted during `set` and `encrypt` ([#862](https://github.com/dotenvx/dotenvx/pull/862))
19
+
20
+ ### Changed
21
+
22
+ * Return exit code 1 from `get` when decryption errors are reported ([#862](https://github.com/dotenvx/dotenvx/pull/862))
23
+
24
+ ### Fixed
25
+
26
+ * Add Windows PowerShell coverage for npm shim encryption of `.env.local` files
6
27
 
7
28
  ## [2.0.0](https://github.com/dotenvx/dotenvx/compare/v1.76.0...v2.0.0) (2026-06-30)
8
29
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0",
2
+ "version": "2.1.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -19,6 +19,7 @@ async function get (key) {
19
19
 
20
20
  const prettyPrint = options.prettyPrint || options.pp
21
21
  const ignore = options.ignore || []
22
+ let errorCount = 0
22
23
 
23
24
  let envs = []
24
25
  // handle shorthand conventions - like --convention=nextjs
@@ -52,6 +53,7 @@ async function get (key) {
52
53
  continue // ignore error
53
54
  }
54
55
 
56
+ errorCount += 1
55
57
  logger.error(error.messageWithHelp)
56
58
  }
57
59
 
@@ -97,6 +99,10 @@ async function get (key) {
97
99
  console.log(JSON.stringify(parsed, null, space))
98
100
  }
99
101
  }
102
+
103
+ if (errorCount > 0) {
104
+ process.exit(1)
105
+ }
100
106
  } catch (error) {
101
107
  if (spinner) spinner.stop()
102
108
  catchAndLog(error)
@@ -7,6 +7,7 @@ const catchAndLog = require('./../../lib/helpers/catchAndLog')
7
7
  const createSpinner = require('../../lib/helpers/createSpinner')
8
8
  const Session = require('../../db/session')
9
9
  const normalizeArmorAliases = require('./normalizeArmorAliases')
10
+ const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
10
11
 
11
12
  const conventions = require('./../../lib/helpers/conventions')
12
13
  const { determine } = require('./../../lib/helpers/envResolution')
@@ -45,7 +46,7 @@ function uniqueInjectedKeys (processedEnvs) {
45
46
  }
46
47
 
47
48
  async function run () {
48
- const options = normalizeArmorAliases(this.opts())
49
+ const options = normalizeDotenvConfigQuiet(normalizeArmorAliases(this.opts()))
49
50
 
50
51
  let commandArgs = this.args
51
52
  if (commandArgs.length < 1) {
@@ -5,6 +5,8 @@ const setTransform = require('./../../lib/transforms/set')
5
5
 
6
6
  const catchAndLog = require('../../lib/helpers/catchAndLog')
7
7
  const createSpinner = require('../../lib/helpers/createSpinner')
8
+ const Errors = require('../../lib/helpers/errors')
9
+ const prompts = require('../../lib/helpers/prompts')
8
10
  const Session = require('../../db/session')
9
11
  const normalizeArmorAliases = require('./normalizeArmorAliases')
10
12
 
@@ -13,9 +15,43 @@ async function set (key, value) {
13
15
 
14
16
  let encrypt = true
15
17
  let settingMessage = 'encrypting'
18
+ let settingSymbol = '◈'
16
19
  if (options.plain) {
17
20
  encrypt = false
18
21
  settingMessage = 'setting'
22
+ settingSymbol = '◇'
23
+ }
24
+
25
+ if (typeof value === 'undefined') {
26
+ if (!process.stdin.isTTY) {
27
+ catchAndLog(new Errors({ key }).missingValue())
28
+ process.exit(1)
29
+ return
30
+ }
31
+
32
+ try {
33
+ value = await prompts.password({
34
+ message: key,
35
+ prefix: settingSymbol,
36
+ separator: '='
37
+ }, {
38
+ input: process.stdin,
39
+ output: process.stderr
40
+ })
41
+ } catch (error) {
42
+ if (error.code === 'PROMPT_CANCELLED') {
43
+ process.exit(130)
44
+ return
45
+ }
46
+
47
+ throw error
48
+ }
49
+ }
50
+
51
+ if (value === '') {
52
+ catchAndLog(new Errors({ key }).missingValue())
53
+ process.exit(1)
54
+ return
19
55
  }
20
56
 
21
57
  const spinner = await createSpinner({ ...options, text: settingMessage })
@@ -12,6 +12,7 @@ const getCommanderVersion = require('./../lib/helpers/getCommanderVersion')
12
12
  const executeDynamic = require('./../lib/helpers/executeDynamic')
13
13
  const removeDynamicHelpSection = require('./../lib/helpers/removeDynamicHelpSection')
14
14
  const removeOptionsHelpParts = require('./../lib/helpers/removeOptionsHelpParts')
15
+ const normalizeDotenvConfigQuiet = require('./../lib/helpers/normalizeDotenvConfigQuiet')
15
16
 
16
17
  // for use with run
17
18
  const envs = []
@@ -38,7 +39,7 @@ program
38
39
  .option('-v, --verbose', 'sets log level to verbose')
39
40
  .option('-d, --debug', 'sets log level to debug')
40
41
  .hook('preAction', (thisCommand, actionCommand) => {
41
- const options = thisCommand.opts()
42
+ const options = normalizeDotenvConfigQuiet(thisCommand.opts())
42
43
 
43
44
  setLogLevel(options)
44
45
  })
@@ -103,12 +104,12 @@ program.command('get')
103
104
 
104
105
  // dotenvx set
105
106
  program.command('set')
106
- .usage('<KEY> <value> [options]')
107
+ .usage('<KEY> [value] [options]')
107
108
  .description('encrypt a single environment variable')
108
109
  .addHelpText('after', examples.set)
109
110
  .allowUnknownOption()
110
111
  .argument('KEY', 'KEY')
111
- .argument('value', 'value')
112
+ .argument('[value]', 'value')
112
113
  .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
113
114
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
114
115
  .option('-c, --encrypt', 'encrypt value', true)
@@ -1,6 +1,7 @@
1
1
  module.exports = {
2
2
  decryptKeyValue: require('./decryptKeyValue'),
3
- isPublicKey: require('./isPublicKey'),
3
+ isDotenvPublicKey: require('./isDotenvPublicKey'),
4
+ isPlainKey: require('./isPlainKey'),
4
5
  mutateKeysSrc: require('./mutateKeysSrc'),
5
6
  mutateSrc: require('./mutateSrc')
6
7
  }
@@ -1,7 +1,7 @@
1
1
  const PUBLIC_KEY_PATTERN = /^DOTENV_PUBLIC_KEY/
2
2
 
3
- function isPublicKey (key) {
3
+ function isDotenvPublicKey (key) {
4
4
  return PUBLIC_KEY_PATTERN.test(key)
5
5
  }
6
6
 
7
- module.exports = isPublicKey
7
+ module.exports = isDotenvPublicKey
@@ -0,0 +1,7 @@
1
+ const PLAIN_KEY_PATTERN = /_PLAIN$/
2
+
3
+ function isPlainKey (key) {
4
+ return PLAIN_KEY_PATTERN.test(key)
5
+ }
6
+
7
+ module.exports = isPlainKey
@@ -18,7 +18,8 @@ const ISSUE_BY_CODE = {
18
18
  MISSING_KEY: 'https://github.com/dotenvx/dotenvx/issues/759',
19
19
  MISSING_LOG_LEVEL: 'must be valid log level',
20
20
  MISSING_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/464',
21
- MISSING_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/new',
21
+ MISSING_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/865',
22
+ MISSING_VALUE: 'https://github.com/dotenvx/dotenvx/issues/864',
22
23
  PRECOMMIT_HOOK_MODIFY_FAILED: 'try again or report error',
23
24
  WRONG_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/466'
24
25
  }
@@ -278,6 +279,18 @@ class Errors {
278
279
  return e
279
280
  }
280
281
 
282
+ missingValue () {
283
+ const code = 'MISSING_VALUE'
284
+ const message = `[${code}] missing value (${this.key})`
285
+ const help = `fix: [${ISSUE_BY_CODE[code]}]`
286
+
287
+ const e = new Error(message)
288
+ e.code = code
289
+ e.help = help
290
+ e.messageWithHelp = `${message}. ${help}`
291
+ return e
292
+ }
293
+
281
294
  precommitHookModifyFailed () {
282
295
  const code = 'PRECOMMIT_HOOK_MODIFY_FAILED'
283
296
  const message = `[${code}] failed to modify pre-commit hook: ${this.error.message}`
@@ -0,0 +1,9 @@
1
+ function normalizeDotenvConfigQuiet (options) {
2
+ if (process.env.DOTENV_CONFIG_QUIET === 'true') {
3
+ options.quiet = true
4
+ }
5
+
6
+ return options
7
+ }
8
+
9
+ module.exports = normalizeDotenvConfigQuiet
@@ -26,6 +26,18 @@ function enquirerOptions (context = {}) {
26
26
  return options
27
27
  }
28
28
 
29
+ function clearLastLine (stream) {
30
+ if (stream && typeof stream.moveCursor === 'function' && typeof stream.clearLine === 'function') {
31
+ stream.moveCursor(0, -1)
32
+ stream.clearLine(0)
33
+ return
34
+ }
35
+
36
+ if (stream && typeof stream.write === 'function') {
37
+ stream.write('\x1B[1A\x1B[2K')
38
+ }
39
+ }
40
+
29
41
  async function select ({ message, choices }, context) {
30
42
  const answer = await enquirer.prompt({
31
43
  type: 'select',
@@ -38,6 +50,40 @@ async function select ({ message, choices }, context) {
38
50
  return answer.value
39
51
  }
40
52
 
53
+ async function password ({ message, prefix, separator }, context) {
54
+ const output = (context && context.output) || process.stderr
55
+
56
+ try {
57
+ const answer = await enquirer.prompt({
58
+ type: 'password',
59
+ name: 'value',
60
+ message,
61
+ symbols: {
62
+ prefix: {
63
+ pending: prefix,
64
+ submitted: prefix,
65
+ cancelled: prefix
66
+ },
67
+ separator: {
68
+ pending: separator,
69
+ submitted: separator,
70
+ cancelled: separator
71
+ }
72
+ },
73
+ ...enquirerOptions(context)
74
+ })
75
+
76
+ clearLastLine(output)
77
+ return answer.value
78
+ } catch (error) {
79
+ clearLastLine(output)
80
+ const e = new Error('prompt cancelled')
81
+ e.code = 'PROMPT_CANCELLED'
82
+ throw e
83
+ }
84
+ }
85
+
41
86
  module.exports = {
87
+ password,
42
88
  select
43
89
  }
package/src/lib/main.js CHANGED
@@ -22,6 +22,7 @@ const { determine } = require('./helpers/envResolution')
22
22
  const fsx = require('./helpers/fsx')
23
23
  const decryptKeyValue = require('./helpers/cryptography/decryptKeyValue')
24
24
  const Errors = require('./helpers/errors')
25
+ const normalizeDotenvConfigQuiet = require('./helpers/normalizeDotenvConfigQuiet')
25
26
 
26
27
  function uniqueInjectedKeys (processedEnvs) {
27
28
  const result = new Set()
@@ -35,6 +36,8 @@ function uniqueInjectedKeys (processedEnvs) {
35
36
 
36
37
  /** @type {import('./main').config} */
37
38
  const config = function (options = {}) {
39
+ options = normalizeDotenvConfigQuiet(options)
40
+
38
41
  // allow user to set processEnv to write to
39
42
  let processEnv = process.env
40
43
  if (options && options.processEnv != null) {
@@ -130,7 +133,7 @@ const config = function (options = {}) {
130
133
  if (readableFilepaths.length > 0) {
131
134
  msg += ` from ${readableFilepaths.join(', ')}`
132
135
  }
133
- logger.successv(msg)
136
+ logger.success(`⟐ ${msg}`)
134
137
 
135
138
  if (lastError) {
136
139
  return { parsed: parsedAll, error: lastError }
@@ -202,6 +205,8 @@ const parse = function (src, options = {}) {
202
205
 
203
206
  /* @type {import('./main').set} */
204
207
  const set = async function (key, value, options = {}) {
208
+ options = normalizeDotenvConfigQuiet(options)
209
+
205
210
  // encrypt
206
211
  let encrypt = true
207
212
  if (options.plain) {
@@ -8,7 +8,7 @@ const SAMPLE_ENV_KIT = require('../helpers/kits/sample')
8
8
  const Errors = require('../helpers/errors')
9
9
  const { determine } = require('./../helpers/envResolution')
10
10
  const detectEncoding = require('./../helpers/detectEncoding')
11
- const { isPublicKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
11
+ const { isDotenvPublicKey, isPlainKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
12
12
  const keynames = require('../conventions/keynames')
13
13
  const PostArmorUp = require('../api/postArmorUp')
14
14
  const prompts = require('../helpers/prompts')
@@ -129,8 +129,7 @@ async function encryptTransform (options = {}) {
129
129
  const { parsed } = scan(row.envSrc, { ik, ek })
130
130
 
131
131
  for (const [key, values] of Object.entries(parsed)) {
132
- // skip if public key
133
- if (isPublicKey(key)) {
132
+ if (isDotenvPublicKey(key) || isPlainKey(key)) {
134
133
  continue
135
134
  }
136
135
 
@@ -7,7 +7,7 @@ const TYPE_ENV_FILE = 'envFile'
7
7
  const getResolver = require('./../resolvers/get')
8
8
  const { determine } = require('./../helpers/envResolution')
9
9
  const detectEncoding = require('./../helpers/detectEncoding')
10
- const { mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
10
+ const { isPlainKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
11
11
  const keynames = require('../conventions/keynames')
12
12
  const Errors = require('../helpers/errors')
13
13
  const PostArmorUp = require('../api/postArmorUp')
@@ -37,7 +37,7 @@ async function setTransform (options = {}) {
37
37
  const fk = options.fk || '.env.keys'
38
38
  let noArmor = options.noArmor // key storage selector below
39
39
  const noCreate = options.noCreate
40
- const noEncrypt = !options.encrypt
40
+ const noEncrypt = !options.encrypt || isPlainKey(key)
41
41
 
42
42
  const processedEnvs = []
43
43
  const changedFilepaths = []