@dotenvx/dotenvx 2.16.1 → 2.17.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 +69 -0
- package/package.json +2 -2
- package/src/cli/actions/get.js +4 -0
- package/src/cli/actions/run.js +4 -0
- package/src/lib/helpers/resolveBitwardenPassword.js +11 -10
- package/src/lib/helpers/resolveOnePassword.js +0 -2
- package/src/lib/resolvers/envs.js +2 -8
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/v2.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.17.1...main)
|
|
6
|
+
|
|
7
|
+
## [2.17.1](https://github.com/dotenvx/dotenvx/compare/v2.17.0...v2.17.1) (2026-07-21)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* Handle `ctrl + c` better ([#916](https://github.com/dotenvx/dotenvx/pull/916))
|
|
12
|
+
|
|
13
|
+
## [2.17.0](https://github.com/dotenvx/dotenvx/compare/v2.16.1...v2.17.0) (2026-07-21)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* Add support for Bitwarden `bw://` secrets in your .env files. ([#915](https://github.com/dotenvx/dotenvx/pull/915))
|
|
6
18
|
|
|
7
19
|
## [2.16.1](https://github.com/dotenvx/dotenvx/compare/v2.16.0...v2.16.1) (2026-07-21)
|
|
8
20
|
|
package/README.md
CHANGED
|
@@ -166,6 +166,19 @@ Hello World
|
|
|
166
166
|
|
|
167
167
|
see [1Password guide](https://dotenvx.com/docs/secrets-in-1password)
|
|
168
168
|
|
|
169
|
+
</details>
|
|
170
|
+
<details><summary>Bitwarden 🛡️</summary><br>
|
|
171
|
+
|
|
172
|
+
Run with secrets resolved directly from Bitwarden Password Manager.
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
$ echo 'HELLO="bw://My Hello Login/password"' > .env
|
|
176
|
+
$ dotenvx run -- sh -c 'echo Hello $HELLO'
|
|
177
|
+
Hello World
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Install the [Bitwarden Password Manager CLI](https://bitwarden.com/help/cli/) before running dotenvx.
|
|
181
|
+
|
|
169
182
|
</details>
|
|
170
183
|
<details><summary>TypeScript 📘</summary><br>
|
|
171
184
|
|
|
@@ -1193,6 +1206,38 @@ $ dotenvx run --no-1password -- node index.js
|
|
|
1193
1206
|
|
|
1194
1207
|
The same flag is available for `dotenvx get` and `dotenvx validate`.
|
|
1195
1208
|
|
|
1209
|
+
</details>
|
|
1210
|
+
<details><summary>`run` - Bitwarden</summary><br>
|
|
1211
|
+
|
|
1212
|
+
Resolve `bw://` references directly from your `.env` file through the [Bitwarden Password Manager CLI](https://bitwarden.com/help/cli/).
|
|
1213
|
+
|
|
1214
|
+
```ini
|
|
1215
|
+
# .env
|
|
1216
|
+
API_KEY="bw://My GitHub Account/password"
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
The reference format is `bw://<item>/<field>`. The item can be a human-readable Bitwarden search term or an exact item UUID. Quote the dotenv value when the item name contains spaces.
|
|
1220
|
+
|
|
1221
|
+
Supported fields are:
|
|
1222
|
+
|
|
1223
|
+
- `username`
|
|
1224
|
+
- `password`
|
|
1225
|
+
- `uri`
|
|
1226
|
+
|
|
1227
|
+
When the vault is locked in an interactive terminal, dotenvx prompts for your Bitwarden master password.
|
|
1228
|
+
|
|
1229
|
+
Human-readable names are convenient, but must identify a single item. If multiple items match, Bitwarden returns an error. Use the item UUID when you need an unambiguous reference.
|
|
1230
|
+
|
|
1231
|
+
If Bitwarden cannot resolve a reference, dotenvx reports the error and leaves the original `bw://` value unresolved.
|
|
1232
|
+
|
|
1233
|
+
Use `--no-bitwarden` to skip Bitwarden resolution intentionally.
|
|
1234
|
+
|
|
1235
|
+
```sh
|
|
1236
|
+
$ dotenvx run --no-bitwarden -- node index.js
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
The same flag is available for `dotenvx get` and `dotenvx validate`.
|
|
1240
|
+
|
|
1196
1241
|
</details>
|
|
1197
1242
|
<details><summary>`run -f <directory>`</summary><br>
|
|
1198
1243
|
|
|
@@ -3310,6 +3355,30 @@ Set `no1Password` to leave `op://` values unresolved and avoid calling `op`.
|
|
|
3310
3355
|
require('@dotenvx/dotenvx').config({no1Password: true})
|
|
3311
3356
|
```
|
|
3312
3357
|
|
|
3358
|
+
</details>
|
|
3359
|
+
<details><summary>`config(noBitwarden:)` - noBitwarden</summary><br>
|
|
3360
|
+
|
|
3361
|
+
By default, `config()` automatically resolves `bw://` values through the installed [Bitwarden Password Manager CLI](https://bitwarden.com/help/cli/).
|
|
3362
|
+
|
|
3363
|
+
```ini
|
|
3364
|
+
# .env
|
|
3365
|
+
API_KEY="bw://My GitHub Account/password"
|
|
3366
|
+
```
|
|
3367
|
+
|
|
3368
|
+
```js
|
|
3369
|
+
// index.js
|
|
3370
|
+
require('@dotenvx/dotenvx').config()
|
|
3371
|
+
|
|
3372
|
+
console.log(process.env.API_KEY)
|
|
3373
|
+
```
|
|
3374
|
+
|
|
3375
|
+
Set `noBitwarden` to leave `bw://` values unresolved and avoid calling `bw`.
|
|
3376
|
+
|
|
3377
|
+
```js
|
|
3378
|
+
// index.js
|
|
3379
|
+
require('@dotenvx/dotenvx').config({noBitwarden: true})
|
|
3380
|
+
```
|
|
3381
|
+
|
|
3313
3382
|
</details>
|
|
3314
3383
|
<details><summary>`parse(src)`</summary><br>
|
|
3315
3384
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.17.1",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a secure dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"funding": "https://dotenvx.com",
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@dotenvx/primitives": "^2.1.0",
|
|
56
|
-
"@dotenvx/tooling": "^1.0.
|
|
56
|
+
"@dotenvx/tooling": "^1.0.3",
|
|
57
57
|
"yocto-spinner": "^1.2.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
package/src/cli/actions/get.js
CHANGED
package/src/cli/actions/run.js
CHANGED
|
@@ -5,7 +5,6 @@ const createSpinner = require('./createSpinner')
|
|
|
5
5
|
const createElapsedStatus = require('./createElapsedStatus')
|
|
6
6
|
|
|
7
7
|
const FIELDS = new Set(['username', 'password', 'uri'])
|
|
8
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
9
8
|
|
|
10
9
|
function execFileAsync (command, args, options) {
|
|
11
10
|
return new Promise((resolve, reject) => {
|
|
@@ -21,9 +20,12 @@ function isSecretReference (value) {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
function parseSecretReference (value) {
|
|
24
|
-
const
|
|
23
|
+
const reference = value.slice('bw://'.length)
|
|
24
|
+
const separatorIndex = reference.lastIndexOf('/')
|
|
25
|
+
const item = reference.slice(0, separatorIndex)
|
|
26
|
+
const field = reference.slice(separatorIndex + 1)
|
|
25
27
|
|
|
26
|
-
if (
|
|
28
|
+
if (separatorIndex < 1 || !field) {
|
|
27
29
|
throw new Error('invalid Bitwarden Password Manager reference')
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -31,7 +33,7 @@ function parseSecretReference (value) {
|
|
|
31
33
|
throw new Error(`unsupported Bitwarden Password Manager field ${field}`)
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
return {
|
|
36
|
+
return { item, field }
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async function session (options) {
|
|
@@ -105,19 +107,19 @@ async function resolveBitwardenPassword (parsed, options = {}) {
|
|
|
105
107
|
if (!isSecretReference(value)) continue
|
|
106
108
|
|
|
107
109
|
try {
|
|
108
|
-
const {
|
|
110
|
+
const { item, field } = parseSecretReference(value)
|
|
109
111
|
if (options.session) startStatus()
|
|
110
112
|
const bwSession = await session(options)
|
|
111
|
-
const stdout = await execFileAsync('bw', ['get', field,
|
|
113
|
+
const stdout = await execFileAsync('bw', ['get', field, item], {
|
|
112
114
|
encoding: 'utf8',
|
|
113
115
|
windowsHide: true,
|
|
114
116
|
env: { ...process.env, BW_SESSION: bwSession }
|
|
115
117
|
})
|
|
116
118
|
parsed[key] = secretValue(stdout)
|
|
117
119
|
} catch (error) {
|
|
120
|
+
if (error && error.code === 'PROMPT_CANCELLED') throw error
|
|
118
121
|
errors.push(resolutionError(key, error))
|
|
119
122
|
unresolved.push(key)
|
|
120
|
-
delete parsed[key]
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
} finally {
|
|
@@ -138,13 +140,13 @@ function resolveBitwardenPasswordSync (parsed) {
|
|
|
138
140
|
if (!isSecretReference(value)) continue
|
|
139
141
|
|
|
140
142
|
try {
|
|
141
|
-
const {
|
|
143
|
+
const { item, field } = parseSecretReference(value)
|
|
142
144
|
if (!process.env.BW_SESSION) {
|
|
143
145
|
const error = new Error('Bitwarden Password Manager requires an unlocked BW_SESSION')
|
|
144
146
|
error.code = 'BW_SESSION_MISSING'
|
|
145
147
|
throw error
|
|
146
148
|
}
|
|
147
|
-
const stdout = execFileSync('bw', ['get', field,
|
|
149
|
+
const stdout = execFileSync('bw', ['get', field, item], {
|
|
148
150
|
encoding: 'utf8',
|
|
149
151
|
windowsHide: true,
|
|
150
152
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
@@ -153,7 +155,6 @@ function resolveBitwardenPasswordSync (parsed) {
|
|
|
153
155
|
} catch (error) {
|
|
154
156
|
errors.push(resolutionError(key, error))
|
|
155
157
|
unresolved.push(key)
|
|
156
|
-
delete parsed[key]
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -44,7 +44,6 @@ async function resolveOnePassword (parsed, options = {}) {
|
|
|
44
44
|
} catch (error) {
|
|
45
45
|
errors.push(resolutionError(key, error))
|
|
46
46
|
unresolved.push(key)
|
|
47
|
-
delete parsed[key]
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
} finally {
|
|
@@ -73,7 +72,6 @@ function resolveOnePasswordSync (parsed) {
|
|
|
73
72
|
} catch (error) {
|
|
74
73
|
errors.push(resolutionError(key, error))
|
|
75
74
|
unresolved.push(key)
|
|
76
|
-
delete parsed[key]
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
|
|
@@ -106,20 +106,19 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
|
|
|
106
106
|
if (!no1Password) {
|
|
107
107
|
const result = await resolveOnePassword(row.injected, { onStatus })
|
|
108
108
|
row.errors.push(...result.errors)
|
|
109
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
110
109
|
Object.assign(row.parsed, row.injected)
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
if (!noBitwarden) {
|
|
114
113
|
const passwordResult = await resolveBitwardenPassword(row.injected, { onStatus })
|
|
115
114
|
row.errors.push(...passwordResult.errors)
|
|
116
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
117
115
|
|
|
118
116
|
Object.assign(row.parsed, row.injected)
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
inject(processEnv, row.parsed)
|
|
122
120
|
} catch (e) {
|
|
121
|
+
if (e.code === 'PROMPT_CANCELLED') throw e
|
|
123
122
|
row.errors = [e]
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -160,14 +159,12 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
|
|
|
160
159
|
if (!no1Password) {
|
|
161
160
|
const result = resolveOnePassword.sync(row.injected)
|
|
162
161
|
row.errors.push(...result.errors)
|
|
163
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
164
162
|
Object.assign(row.parsed, row.injected)
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
if (!noBitwarden) {
|
|
168
166
|
const passwordResult = resolveBitwardenPassword.sync(row.injected)
|
|
169
167
|
row.errors.push(...passwordResult.errors)
|
|
170
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
171
168
|
|
|
172
169
|
Object.assign(row.parsed, row.injected)
|
|
173
170
|
}
|
|
@@ -217,20 +214,19 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
|
|
|
217
214
|
if (!no1Password) {
|
|
218
215
|
const result = await resolveOnePassword(row.injected, { onStatus })
|
|
219
216
|
row.errors.push(...result.errors)
|
|
220
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
221
217
|
Object.assign(row.parsed, row.injected)
|
|
222
218
|
}
|
|
223
219
|
|
|
224
220
|
if (!noBitwarden) {
|
|
225
221
|
const passwordResult = await resolveBitwardenPassword(row.injected, { onStatus })
|
|
226
222
|
row.errors.push(...passwordResult.errors)
|
|
227
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
228
223
|
|
|
229
224
|
Object.assign(row.parsed, row.injected)
|
|
230
225
|
}
|
|
231
226
|
|
|
232
227
|
inject(processEnv, parsed)
|
|
233
228
|
} catch (e) {
|
|
229
|
+
if (e.code === 'PROMPT_CANCELLED') throw e
|
|
234
230
|
if (e.code === 'ENOENT' || e.code === 'EISDIR') {
|
|
235
231
|
row.errors = [new Errors({ envFilepath: env.value, filepath }).missingEnvFile()]
|
|
236
232
|
} else {
|
|
@@ -278,14 +274,12 @@ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provid
|
|
|
278
274
|
if (!no1Password) {
|
|
279
275
|
const result = resolveOnePassword.sync(row.injected)
|
|
280
276
|
row.errors.push(...result.errors)
|
|
281
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
282
277
|
Object.assign(row.parsed, row.injected)
|
|
283
278
|
}
|
|
284
279
|
|
|
285
280
|
if (!noBitwarden) {
|
|
286
281
|
const passwordResult = resolveBitwardenPassword.sync(row.injected)
|
|
287
282
|
row.errors.push(...passwordResult.errors)
|
|
288
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
289
283
|
|
|
290
284
|
Object.assign(row.parsed, row.injected)
|
|
291
285
|
}
|