@dotenvx/dotenvx 2.15.0 → 2.15.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,13 @@
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.15.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.15.1...main)
6
+
7
+ ## [2.15.1](https://github.com/dotenvx/dotenvx/compare/v2.15.0...v2.15.1) (2026-07-20)
8
+
9
+ ### Changed
10
+
11
+ * Better communicate time waiting on 1password with elapsed time ([#909](https://github.com/dotenvx/dotenvx/pull/909))
6
12
 
7
13
  ## [2.15.0](https://github.com/dotenvx/dotenvx/compare/v2.14.0...v2.15.0) (2026-07-20)
8
14
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.15.0",
2
+ "version": "2.15.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -0,0 +1,17 @@
1
+ function createElapsedStatus (onStatus, text) {
2
+ if (!onStatus) return () => {}
3
+
4
+ let elapsed = 0
5
+ onStatus(text)
6
+
7
+ const timer = setInterval(() => {
8
+ elapsed += 1
9
+ onStatus(`${text} (${elapsed}s)`)
10
+ }, 1000)
11
+
12
+ if (timer.unref) timer.unref()
13
+
14
+ return () => clearInterval(timer)
15
+ }
16
+
17
+ module.exports = createElapsedStatus
@@ -2,6 +2,7 @@ const { execFile, execFileSync } = require('child_process')
2
2
  const Errors = require('./errors')
3
3
  const prompts = require('./prompts')
4
4
  const createSpinner = require('./createSpinner')
5
+ const createElapsedStatus = require('./createElapsedStatus')
5
6
 
6
7
  const FIELDS = new Set(['username', 'password', 'uri'])
7
8
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
@@ -47,6 +48,7 @@ async function session (options) {
47
48
  output: process.stderr
48
49
  })
49
50
  createSpinner.resume()
51
+ if (options.startStatus) options.startStatus()
50
52
  const passwordEnv = 'DOTENVX_BITWARDEN_PASSWORD'
51
53
  options.session = secretValue(await execFileAsync('bw', ['unlock', '--passwordenv', passwordEnv, '--raw'], {
52
54
  encoding: 'utf8',
@@ -92,23 +94,36 @@ async function resolveBitwardenPassword (parsed, options = {}) {
92
94
  const errors = []
93
95
  const unresolved = []
94
96
  options.session = options.session || process.env.BW_SESSION
95
-
96
- for (const [key, value] of Object.entries(parsed)) {
97
- if (!isSecretReference(value)) continue
98
-
99
- try {
100
- const { itemId, field } = parseSecretReference(value)
101
- const bwSession = await session(options)
102
- const stdout = await execFileAsync('bw', ['get', field, itemId], {
103
- encoding: 'utf8',
104
- windowsHide: true,
105
- env: { ...process.env, BW_SESSION: bwSession }
106
- })
107
- parsed[key] = secretValue(stdout)
108
- } catch (error) {
109
- errors.push(resolutionError(key, error))
110
- unresolved.push(key)
111
- delete parsed[key]
97
+ let stopStatus
98
+ const startStatus = () => {
99
+ if (!stopStatus) stopStatus = createElapsedStatus(options.onStatus, 'awaiting bitwarden')
100
+ }
101
+ options.startStatus = startStatus
102
+
103
+ try {
104
+ for (const [key, value] of Object.entries(parsed)) {
105
+ if (!isSecretReference(value)) continue
106
+
107
+ try {
108
+ const { itemId, field } = parseSecretReference(value)
109
+ if (options.session) startStatus()
110
+ const bwSession = await session(options)
111
+ const stdout = await execFileAsync('bw', ['get', field, itemId], {
112
+ encoding: 'utf8',
113
+ windowsHide: true,
114
+ env: { ...process.env, BW_SESSION: bwSession }
115
+ })
116
+ parsed[key] = secretValue(stdout)
117
+ } catch (error) {
118
+ errors.push(resolutionError(key, error))
119
+ unresolved.push(key)
120
+ delete parsed[key]
121
+ }
122
+ }
123
+ } finally {
124
+ if (stopStatus) {
125
+ stopStatus()
126
+ if (options.onStatus) options.onStatus('injecting')
112
127
  }
113
128
  }
114
129
 
@@ -1,5 +1,6 @@
1
1
  const { execFile, execFileSync } = require('child_process')
2
2
  const Errors = require('./errors')
3
+ const createElapsedStatus = require('./createElapsedStatus')
3
4
 
4
5
  function execFileAsync (command, args, options) {
5
6
  return new Promise((resolve, reject) => {
@@ -22,23 +23,34 @@ function resolutionError (key, error) {
22
23
  return new Errors({ message }).onePasswordFailed()
23
24
  }
24
25
 
25
- async function resolveOnePassword (parsed) {
26
+ async function resolveOnePassword (parsed, options = {}) {
26
27
  const errors = []
27
28
  const unresolved = []
29
+ const hasSecretReferences = Object.values(parsed).some(isSecretReference)
30
+ const stopStatus = hasSecretReferences
31
+ ? createElapsedStatus(options.onStatus, 'awaiting 1password')
32
+ : null
28
33
 
29
- for (const [key, value] of Object.entries(parsed)) {
30
- if (!isSecretReference(value)) continue
34
+ try {
35
+ for (const [key, value] of Object.entries(parsed)) {
36
+ if (!isSecretReference(value)) continue
31
37
 
32
- try {
33
- const stdout = await execFileAsync('op', ['read', value, '--no-newline'], {
34
- encoding: 'utf8',
35
- windowsHide: true
36
- })
37
- parsed[key] = stdout
38
- } catch (error) {
39
- errors.push(resolutionError(key, error))
40
- unresolved.push(key)
41
- delete parsed[key]
38
+ try {
39
+ const stdout = await execFileAsync('op', ['read', value, '--no-newline'], {
40
+ encoding: 'utf8',
41
+ windowsHide: true
42
+ })
43
+ parsed[key] = stdout
44
+ } catch (error) {
45
+ errors.push(resolutionError(key, error))
46
+ unresolved.push(key)
47
+ delete parsed[key]
48
+ }
49
+ }
50
+ } finally {
51
+ if (stopStatus) {
52
+ stopStatus()
53
+ if (options.onStatus) options.onStatus('injecting')
42
54
  }
43
55
  }
44
56
 
@@ -72,7 +72,7 @@ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, d
72
72
  return options
73
73
  }
74
74
 
75
- async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden }) {
75
+ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden, onStatus }) {
76
76
  const row = {}
77
77
  row.type = TYPE_ENV
78
78
  row.string = env.value
@@ -104,14 +104,14 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
104
104
  row.existed = existed || {}
105
105
 
106
106
  if (!no1Password) {
107
- const result = await resolveOnePassword(row.injected)
107
+ const result = await resolveOnePassword(row.injected, { onStatus })
108
108
  row.errors.push(...result.errors)
109
109
  for (const key of result.unresolved) delete row.parsed[key]
110
110
  Object.assign(row.parsed, row.injected)
111
111
  }
112
112
 
113
113
  if (!noBitwarden) {
114
- const passwordResult = await resolveBitwardenPassword(row.injected)
114
+ const passwordResult = await resolveBitwardenPassword(row.injected, { onStatus })
115
115
  row.errors.push(...passwordResult.errors)
116
116
  for (const key of passwordResult.unresolved) delete row.parsed[key]
117
117
 
@@ -180,7 +180,7 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
180
180
  return row
181
181
  }
182
182
 
183
- async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden }) {
183
+ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden, onStatus }) {
184
184
  const row = {}
185
185
  row.type = TYPE_ENV_FILE
186
186
  row.filepath = env.value
@@ -215,14 +215,14 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
215
215
  row.existed = existed || {}
216
216
 
217
217
  if (!no1Password) {
218
- const result = await resolveOnePassword(row.injected)
218
+ const result = await resolveOnePassword(row.injected, { onStatus })
219
219
  row.errors.push(...result.errors)
220
220
  for (const key of result.unresolved) delete row.parsed[key]
221
221
  Object.assign(row.parsed, row.injected)
222
222
  }
223
223
 
224
224
  if (!noBitwarden) {
225
- const passwordResult = await resolveBitwardenPassword(row.injected)
225
+ const passwordResult = await resolveBitwardenPassword(row.injected, { onStatus })
226
226
  row.errors.push(...passwordResult.errors)
227
227
  for (const key of passwordResult.unresolved) delete row.parsed[key]
228
228
 
@@ -320,7 +320,8 @@ async function envs (options = {}) {
320
320
  decryptor,
321
321
  readableFilepaths,
322
322
  no1Password: options.no1Password,
323
- noBitwarden: options.noBitwarden
323
+ noBitwarden: options.noBitwarden,
324
+ onStatus: options.onStatus
324
325
  }))
325
326
  } else if (env.type === TYPE_ENV) {
326
327
  processedEnvs.push(await injectEnv({
@@ -331,7 +332,8 @@ async function envs (options = {}) {
331
332
  provider,
332
333
  decryptor,
333
334
  no1Password: options.no1Password,
334
- noBitwarden: options.noBitwarden
335
+ noBitwarden: options.noBitwarden,
336
+ onStatus: options.onStatus
335
337
  }))
336
338
  }
337
339
  }