@dotenvx/dotenvx 1.70.0 → 1.71.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,19 @@
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.70.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.71.1...main)
6
+
7
+ ## [1.71.1](https://github.com/dotenvx/dotenvx/compare/v1.71.0...v1.71.1) (2026-06-08)
8
+
9
+ ### Changed
10
+
11
+ * stderr changed to `inherit` for dotenvx-armor integration ([#833](https://github.com/dotenvx/dotenvx/pull/833))
12
+
13
+ ## [1.71.0](https://github.com/dotenvx/dotenvx/compare/v1.70.0...v1.71.0) (2026-06-01)
14
+
15
+ ### Added
16
+
17
+ * Add optional automation `--token` support for Armor ⛨ users ([#831](https://github.com/dotenvx/dotenvx/pull/831))
6
18
 
7
19
  ## [1.70.0](https://github.com/dotenvx/dotenvx/compare/v1.69.2...v1.70.0) (2026-05-31)
8
20
 
package/README.md CHANGED
@@ -1286,6 +1286,15 @@ $ dotenvx set HELLO Dotenvx -fk .env.keys -f apps/app1/.env
1286
1286
  $ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
1287
1287
  ```
1288
1288
 
1289
+ </details>
1290
+ <details><summary>`run --token`</summary><br>
1291
+
1292
+ Set Armor ⛨ token for retrieving armored private keys.
1293
+
1294
+ ```sh
1295
+ $ dotenvx run --token "$DOTENVX_ARMOR_TOKEN" -- yourcommand
1296
+ ```
1297
+
1289
1298
  </details>
1290
1299
  <details><summary>`run --no-armor`</summary><br>
1291
1300
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.70.0",
2
+ "version": "1.71.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -9,32 +9,12 @@ const prompts = require('../../lib/helpers/prompts')
9
9
  const Session = require('../../db/session')
10
10
  const normalizeArmorOptions = require('./normalizeArmorOptions')
11
11
 
12
- function keypairSpinnerHooks (spinner) {
13
- let stoppedForArmor = false
14
-
15
- return {
16
- onStderr: () => {
17
- if (spinner && !stoppedForArmor) {
18
- spinner.stop()
19
- stoppedForArmor = true
20
- }
21
- },
22
- after: () => {
23
- if (spinner && stoppedForArmor) {
24
- spinner.start('encrypting')
25
- stoppedForArmor = false
26
- }
27
- }
28
- }
29
- }
30
-
31
- function keyStorageSelector (spinner) {
12
+ function keyStorageSelector () {
32
13
  let selected
33
14
 
34
15
  return async function selectKeyStorage () {
35
16
  if (selected) return selected
36
17
 
37
- if (spinner) spinner.stop()
38
18
  selected = await prompts.select({
39
19
  message: 'Choose private key storage',
40
20
  choices: [
@@ -45,19 +25,16 @@ function keyStorageSelector (spinner) {
45
25
  input: process.stdin,
46
26
  output: process.stderr
47
27
  })
48
- if (spinner) spinner.start('encrypting')
49
28
 
50
29
  return selected
51
30
  }
52
31
  }
53
32
 
54
- function encryptOptions (spinner, noArmor) {
55
- const options = {
56
- keypairHooks: keypairSpinnerHooks(spinner)
57
- }
33
+ function encryptOptions (noArmor) {
34
+ const options = {}
58
35
 
59
36
  if (!noArmor) {
60
- options.selectKeyStorage = keyStorageSelector(spinner)
37
+ options.selectKeyStorage = keyStorageSelector()
61
38
  }
62
39
 
63
40
  return options
@@ -76,9 +53,10 @@ async function encrypt () {
76
53
 
77
54
  // stdout - should not have a try so that exit codes can surface to stdout
78
55
  if (options.stdout) {
56
+ if (spinner) spinner.stop()
79
57
  const {
80
58
  processedEnvs
81
- } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(spinner, noArmor)).run()
59
+ } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(noArmor)).run()
82
60
  if (spinner) spinner.stop()
83
61
  for (const processedEnv of processedEnvs) {
84
62
  console.log(processedEnv.envSrc)
@@ -86,11 +64,12 @@ async function encrypt () {
86
64
  process.exit(0) // exit early
87
65
  } else {
88
66
  try {
67
+ if (spinner) spinner.stop()
89
68
  const {
90
69
  processedEnvs,
91
70
  changedFilepaths,
92
71
  unchangedFilepaths
93
- } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(spinner, noArmor)).run()
72
+ } = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noArmor, noCreate, options.token, encryptOptions(noArmor)).run()
94
73
 
95
74
  for (const processedEnv of processedEnvs) {
96
75
  logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -18,6 +18,7 @@ async function keypair (key) {
18
18
 
19
19
  const sesh = new Session()
20
20
  const noArmor = options.armor === false || await sesh.noArmor()
21
+ if (spinner) spinner.stop()
21
22
  const keypairs = await new Keypair(options.envFile, options.envKeysFile, noArmor).run()
22
23
  const results = key ? keypairs[key] : keypairs
23
24
 
@@ -22,7 +22,7 @@ async function run () {
22
22
  const ignore = options.ignore || []
23
23
 
24
24
  const sesh = new Session()
25
- const noArmor = options.armor === false || (await sesh.noArmor())
25
+ const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
26
26
 
27
27
  if (commandArgs.length < 1) {
28
28
  if (spinner) spinner.stop()
@@ -48,6 +48,7 @@ async function run () {
48
48
  envs = this.envs
49
49
  }
50
50
  envs = determine(envs, process.env)
51
+ if (spinner) spinner.stop()
51
52
 
52
53
  const {
53
54
  processedEnvs,
@@ -55,11 +56,7 @@ async function run () {
55
56
  readableFilepaths,
56
57
  uniqueInjectedKeys
57
58
  } = await new Run(envs, options.overload, process.env, options.envKeysFile, noArmor, {
58
- keypairHooks: {
59
- after: () => {
60
- if (spinner) spinner.start('injecting')
61
- }
62
- }
59
+ token: options.token
63
60
  }).run()
64
61
 
65
62
  for (const processedEnv of processedEnvs) {
@@ -70,6 +70,7 @@ program.command('run')
70
70
  .option('--strict', 'process.exit(1) on any errors', false)
71
71
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
72
72
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
73
+ .option('--token <token>', 'set Armor ⛨ token')
73
74
  .option('--no-armor', 'disable dotenvx-armor features')
74
75
  .addOption(new Option('--no-vlt', 'disable dotenvx-vlt features (deprecated. use --no-armor)').hideHelp())
75
76
  .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
@@ -46,9 +46,7 @@ class Armor {
46
46
  if (publicKey) args.push(publicKey)
47
47
 
48
48
  try {
49
- return JSON.parse(await this._execInteractive(binary, args, {
50
- onStderr: options.onStderr
51
- }))
49
+ return JSON.parse(await this._execInteractive(binary, args))
52
50
  } catch (_e) {
53
51
  return {}
54
52
  }
@@ -113,26 +111,17 @@ class Armor {
113
111
  return childProcess.execFileSync(binary, args).toString().trim()
114
112
  }
115
113
 
116
- _execInteractive (binary, args, options = {}) {
114
+ _execInteractive (binary, args) {
117
115
  return new Promise((resolve, reject) => {
118
116
  const spawnOptions = {
119
- stdio: ['inherit', 'pipe', 'pipe']
117
+ stdio: ['inherit', 'pipe', 'inherit']
120
118
  }
121
119
  const subprocess = childProcess.spawn(binary, args, spawnOptions)
122
120
  let stdout = ''
123
- let sawStderr = false
124
121
 
125
122
  subprocess.stdout.on('data', (data) => {
126
123
  stdout += data.toString()
127
124
  })
128
- subprocess.stderr.on('data', (data) => {
129
- if (!sawStderr) {
130
- sawStderr = true
131
- if (options.onStderr) options.onStderr()
132
- }
133
-
134
- process.stderr.write(data)
135
- })
136
125
  subprocess.on('error', reject)
137
126
  subprocess.on('close', (code) => {
138
127
  if (code !== 0) {
@@ -1,22 +1,12 @@
1
1
  const Armor = require('../../extensions/armor')
2
2
 
3
3
  async function armorKeypair (existingPublicKey, options = {}) {
4
- const hooks = options.hooks || {}
5
- if (hooks.before) await hooks.before()
6
-
7
4
  const keypairOptions = {
8
5
  token: options.token,
9
6
  envFilepath: options.envFilepath
10
7
  }
11
- if (hooks.onStderr) keypairOptions.onStderr = hooks.onStderr
12
- if (hooks.before || hooks.onStderr || hooks.after) keypairOptions.noSpinner = true
13
8
 
14
- let kp
15
- try {
16
- kp = await new Armor().keypair(existingPublicKey, keypairOptions)
17
- } finally {
18
- if (hooks.after) await hooks.after()
19
- }
9
+ const kp = await new Armor().keypair(existingPublicKey, keypairOptions)
20
10
 
21
11
  const publicKey = kp.public_key
22
12
  const privateKey = kp.private_key
@@ -4,7 +4,7 @@ const armorKeypair = require('./armorKeypair')
4
4
  const localKeypair = require('./localKeypair')
5
5
  const { keyNames } = require('../keyResolution')
6
6
 
7
- async function provision ({ envSrc, envFilepath, keysFilepath, noArmor, token, keypairHooks, selectKeyStorage }) {
7
+ async function provision ({ envSrc, envFilepath, keysFilepath, noArmor, token, selectKeyStorage }) {
8
8
  noArmor = noArmor !== false
9
9
  if (!noArmor && selectKeyStorage) {
10
10
  noArmor = await selectKeyStorage() !== 'armored'
@@ -24,7 +24,8 @@ async function provision ({ envSrc, envFilepath, keysFilepath, noArmor, token, k
24
24
  publicKey = kp.publicKey
25
25
  privateKey = kp.privateKey
26
26
  } else {
27
- const kp = await armorKeypair(undefined, { token, envFilepath, hooks: keypairHooks })
27
+ const armorOptions = { token, envFilepath }
28
+ const kp = await armorKeypair(undefined, armorOptions)
28
29
  publicKey = kp.publicKey
29
30
  privateKey = kp.privateKey
30
31
  }
@@ -73,7 +73,11 @@ async function keyValues (filepath, opts = {}) {
73
73
 
74
74
  // armor
75
75
  if (!noArmor && !privateKey && publicKey && publicKey.length > 0) {
76
- const kp = await armorKeypair(publicKey, { envFilepath: filepath, hooks: opts.keypairHooks })
76
+ const armorOptions = { envFilepath: filepath }
77
+ if (opts.token) {
78
+ armorOptions.token = opts.token
79
+ }
80
+ const kp = await armorKeypair(publicKey, armorOptions)
77
81
  privateKey = kp.privateKey
78
82
  privateKeySource = 'armor'
79
83
  }
@@ -53,7 +53,11 @@ function keyValuesFromEnvSrc (src, privateKeyName = null, opts = {}) {
53
53
  }
54
54
 
55
55
  if (!noArmor && !privateKeyValue && publicKeyValue && publicKeyValue.length > 0) {
56
- const kp = armorKeypairSync(publicKeyValue)
56
+ const armorOptions = {}
57
+ if (opts.token) {
58
+ armorOptions.token = opts.token
59
+ }
60
+ const kp = armorKeypairSync(publicKeyValue, armorOptions)
57
61
  privateKeyValue = kp.privateKey
58
62
  privateKeySource = 'armor'
59
63
  }
@@ -74,6 +74,9 @@ function keyValuesSync (filepath, opts = {}) {
74
74
  // armor
75
75
  if (!noArmor && !privateKey && publicKey && publicKey.length > 0) {
76
76
  const armorOptions = { envFilepath: filepath }
77
+ if (opts.token) {
78
+ armorOptions.token = opts.token
79
+ }
77
80
  if (opts.noSpinner) {
78
81
  armorOptions.noSpinner = true
79
82
  }
package/src/lib/main.d.ts CHANGED
@@ -154,6 +154,13 @@ export interface DotenvConfigOptions {
154
154
  */
155
155
  noSpinner?: boolean;
156
156
 
157
+ /**
158
+ * Set Armor token.
159
+ *
160
+ * @example require('@dotenvx/dotenvx').config({ token: process.env.DOTENVX_ARMOR_TOKEN })
161
+ */
162
+ token?: string;
163
+
157
164
  logLevel?:
158
165
  | 'error'
159
166
  | 'warn'
package/src/lib/main.js CHANGED
@@ -60,7 +60,8 @@ const config = function (options = {}) {
60
60
  readableFilepaths,
61
61
  uniqueInjectedKeys
62
62
  } = new Run(envs, overload, processEnv, envKeysFile, noArmor, {
63
- noSpinner: options.noSpinner
63
+ noSpinner: options.noSpinner,
64
+ token: options.token
64
65
  }).runSync()
65
66
 
66
67
  let lastError
@@ -324,7 +325,7 @@ const keypair = function (envFile, key, envKeysFile = null, noArmor = false) {
324
325
 
325
326
  function resolveNoArmor (options = {}) {
326
327
  const sesh = new Session()
327
- return options.noArmor === true || options.noVlt === true || options.noOps === true || sesh.noArmorSync()
328
+ return options.noArmor === true || options.noVlt === true || options.noOps === true || (!options.token && sesh.noArmorSync())
328
329
  }
329
330
 
330
331
  module.exports = {
@@ -37,7 +37,6 @@ class Encrypt {
37
37
  this.noArmor = noArmor
38
38
  this.noCreate = noCreate
39
39
  this.token = token
40
- this.keypairHooks = options.keypairHooks
41
40
  this.selectKeyStorage = options.selectKeyStorage
42
41
 
43
42
  this.processedEnvs = []
@@ -105,8 +104,7 @@ class Encrypt {
105
104
  const { publicKeyName, privateKeyName } = keyNames(envFilepath)
106
105
  const { publicKeyValue, privateKeyValue } = await keyValues(envFilepath, {
107
106
  keysFilepath: this.envKeysFilepath,
108
- noArmor: this.noArmor,
109
- keypairHooks: this.keypairHooks
107
+ noArmor: this.noArmor
110
108
  })
111
109
 
112
110
  // first pass - provision
@@ -117,7 +115,6 @@ class Encrypt {
117
115
  keysFilepath: this.envKeysFilepath,
118
116
  noArmor: this.noArmor,
119
117
  token: this.token,
120
- keypairHooks: this.keypairHooks,
121
118
  selectKeyStorage: this.selectKeyStorage
122
119
  })
123
120
  envSrc = prov.envSrc
@@ -24,7 +24,7 @@ class Run {
24
24
  this.envKeysFilepath = envKeysFilepath
25
25
  this.noArmor = noArmor
26
26
  this.noSpinner = options.noSpinner
27
- this.keypairHooks = options.keypairHooks
27
+ this.token = options.token
28
28
 
29
29
  this.processedEnvs = []
30
30
  this.readableFilepaths = new Set()
@@ -95,7 +95,7 @@ class Run {
95
95
  privateKeyName: resolvedPrivateKeyName,
96
96
  privateKeyValue,
97
97
  privateKeySource
98
- } = keyValuesFromEnvSrc(env, privateKeyName, { keysFilepath: this.envKeysFilepath, noArmor: this.noArmor, processEnv: this.processEnv })
98
+ } = keyValuesFromEnvSrc(env, privateKeyName, { keysFilepath: this.envKeysFilepath, noArmor: this.noArmor, processEnv: this.processEnv, token: this.token })
99
99
 
100
100
  const {
101
101
  parsed,
@@ -144,7 +144,8 @@ class Run {
144
144
  const { privateKeyValue, privateKeySource } = keyValuesSync(filepath, {
145
145
  keysFilepath: this.envKeysFilepath,
146
146
  noArmor: this.noArmor,
147
- noSpinner: this.noSpinner
147
+ noSpinner: this.noSpinner,
148
+ token: this.token
148
149
  })
149
150
 
150
151
  const {
@@ -197,7 +198,7 @@ class Run {
197
198
  const { privateKeyValue, privateKeySource } = await keyValues(filepath, {
198
199
  keysFilepath: this.envKeysFilepath,
199
200
  noArmor: this.noArmor,
200
- keypairHooks: this.keypairHooks
201
+ token: this.token
201
202
  })
202
203
 
203
204
  const {