@dotenvx/dotenvx 2.16.0 → 2.17.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,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.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.17.0](https://github.com/dotenvx/dotenvx/compare/v2.16.1...v2.17.0) (2026-07-21)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add support for Bitwarden `bw://` secrets in your .env files. ([#915](https://github.com/dotenvx/dotenvx/pull/915))
|
|
12
|
+
|
|
13
|
+
## [2.16.1](https://github.com/dotenvx/dotenvx/compare/v2.16.0...v2.16.1) (2026-07-21)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* Fix: Do not report child error - let child do so. ([#914](https://github.com/dotenvx/dotenvx/pull/914))
|
|
6
18
|
|
|
7
19
|
## [2.16.0](https://github.com/dotenvx/dotenvx/compare/v2.15.1...v2.16.0) (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
|
@@ -174,14 +174,18 @@ async function executeCommand (commandArgs, env, sensitiveValues = []) {
|
|
|
174
174
|
|
|
175
175
|
if (exitCode !== 0) {
|
|
176
176
|
logger.debug(`received exitCode ${exitCode}`)
|
|
177
|
-
|
|
177
|
+
const error = new Errors({ exitCode }).commandExitedWithCode()
|
|
178
|
+
error.exitCode = exitCode
|
|
179
|
+
throw error
|
|
178
180
|
}
|
|
179
181
|
} catch (error) {
|
|
182
|
+
const commandExited = Number.isInteger(error.exitCode) && (error.code === 'COMMAND_EXITED_WITH_CODE' || error.command)
|
|
183
|
+
|
|
180
184
|
// no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
|
|
181
185
|
if (!['SIGINT', 'SIGTERM'].includes(signalSent || error.signal)) {
|
|
182
186
|
if (error.code === 'ENOENT') {
|
|
183
187
|
logger.error(`Unknown command: ${error.command}`)
|
|
184
|
-
} else {
|
|
188
|
+
} else if (!commandExited) {
|
|
185
189
|
logger.error(redactOutput(error.message, sensitiveValues))
|
|
186
190
|
}
|
|
187
191
|
}
|
|
@@ -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,10 +107,10 @@ 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 }
|
|
@@ -117,7 +119,6 @@ async function resolveBitwardenPassword (parsed, options = {}) {
|
|
|
117
119
|
} catch (error) {
|
|
118
120
|
errors.push(resolutionError(key, error))
|
|
119
121
|
unresolved.push(key)
|
|
120
|
-
delete parsed[key]
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
} finally {
|
|
@@ -138,13 +139,13 @@ function resolveBitwardenPasswordSync (parsed) {
|
|
|
138
139
|
if (!isSecretReference(value)) continue
|
|
139
140
|
|
|
140
141
|
try {
|
|
141
|
-
const {
|
|
142
|
+
const { item, field } = parseSecretReference(value)
|
|
142
143
|
if (!process.env.BW_SESSION) {
|
|
143
144
|
const error = new Error('Bitwarden Password Manager requires an unlocked BW_SESSION')
|
|
144
145
|
error.code = 'BW_SESSION_MISSING'
|
|
145
146
|
throw error
|
|
146
147
|
}
|
|
147
|
-
const stdout = execFileSync('bw', ['get', field,
|
|
148
|
+
const stdout = execFileSync('bw', ['get', field, item], {
|
|
148
149
|
encoding: 'utf8',
|
|
149
150
|
windowsHide: true,
|
|
150
151
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
@@ -153,7 +154,6 @@ function resolveBitwardenPasswordSync (parsed) {
|
|
|
153
154
|
} catch (error) {
|
|
154
155
|
errors.push(resolutionError(key, error))
|
|
155
156
|
unresolved.push(key)
|
|
156
|
-
delete parsed[key]
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -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,14 +106,12 @@ 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
|
}
|
|
@@ -160,14 +158,12 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
|
|
|
160
158
|
if (!no1Password) {
|
|
161
159
|
const result = resolveOnePassword.sync(row.injected)
|
|
162
160
|
row.errors.push(...result.errors)
|
|
163
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
164
161
|
Object.assign(row.parsed, row.injected)
|
|
165
162
|
}
|
|
166
163
|
|
|
167
164
|
if (!noBitwarden) {
|
|
168
165
|
const passwordResult = resolveBitwardenPassword.sync(row.injected)
|
|
169
166
|
row.errors.push(...passwordResult.errors)
|
|
170
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
171
167
|
|
|
172
168
|
Object.assign(row.parsed, row.injected)
|
|
173
169
|
}
|
|
@@ -217,14 +213,12 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
|
|
|
217
213
|
if (!no1Password) {
|
|
218
214
|
const result = await resolveOnePassword(row.injected, { onStatus })
|
|
219
215
|
row.errors.push(...result.errors)
|
|
220
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
221
216
|
Object.assign(row.parsed, row.injected)
|
|
222
217
|
}
|
|
223
218
|
|
|
224
219
|
if (!noBitwarden) {
|
|
225
220
|
const passwordResult = await resolveBitwardenPassword(row.injected, { onStatus })
|
|
226
221
|
row.errors.push(...passwordResult.errors)
|
|
227
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
228
222
|
|
|
229
223
|
Object.assign(row.parsed, row.injected)
|
|
230
224
|
}
|
|
@@ -278,14 +272,12 @@ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provid
|
|
|
278
272
|
if (!no1Password) {
|
|
279
273
|
const result = resolveOnePassword.sync(row.injected)
|
|
280
274
|
row.errors.push(...result.errors)
|
|
281
|
-
for (const key of result.unresolved) delete row.parsed[key]
|
|
282
275
|
Object.assign(row.parsed, row.injected)
|
|
283
276
|
}
|
|
284
277
|
|
|
285
278
|
if (!noBitwarden) {
|
|
286
279
|
const passwordResult = resolveBitwardenPassword.sync(row.injected)
|
|
287
280
|
row.errors.push(...passwordResult.errors)
|
|
288
|
-
for (const key of passwordResult.unresolved) delete row.parsed[key]
|
|
289
281
|
|
|
290
282
|
Object.assign(row.parsed, row.injected)
|
|
291
283
|
}
|