@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 +13 -1
- package/README.md +9 -0
- package/package.json +1 -1
- package/src/cli/actions/encrypt.js +8 -29
- package/src/cli/actions/keypair.js +1 -0
- package/src/cli/actions/run.js +3 -6
- package/src/cli/dotenvx.js +1 -0
- package/src/lib/extensions/armor.js +3 -14
- package/src/lib/helpers/cryptography/armorKeypair.js +1 -11
- package/src/lib/helpers/cryptography/provision.js +3 -2
- package/src/lib/helpers/keyResolution/keyValues.js +5 -1
- package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +5 -1
- package/src/lib/helpers/keyResolution/keyValuesSync.js +3 -0
- package/src/lib/main.d.ts +7 -0
- package/src/lib/main.js +3 -2
- package/src/lib/services/encrypt.js +1 -4
- package/src/lib/services/run.js +5 -4
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.
|
|
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
|
@@ -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
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
package/src/cli/actions/run.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -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
|
|
114
|
+
_execInteractive (binary, args) {
|
|
117
115
|
return new Promise((resolve, reject) => {
|
|
118
116
|
const spawnOptions = {
|
|
119
|
-
stdio: ['inherit', '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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
package/src/lib/services/run.js
CHANGED
|
@@ -24,7 +24,7 @@ class Run {
|
|
|
24
24
|
this.envKeysFilepath = envKeysFilepath
|
|
25
25
|
this.noArmor = noArmor
|
|
26
26
|
this.noSpinner = options.noSpinner
|
|
27
|
-
this.
|
|
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
|
-
|
|
201
|
+
token: this.token
|
|
201
202
|
})
|
|
202
203
|
|
|
203
204
|
const {
|