@dotenvx/dotenvx 2.14.0 → 2.15.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,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.14.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.15.0...main)
6
+
7
+ ## [2.15.0](https://github.com/dotenvx/dotenvx/compare/v2.14.0...v2.15.0) (2026-07-20)
8
+
9
+ ### Added
10
+
11
+ * Add support for 1Password `op://` secrets in your .env files. ([#908](https://github.com/dotenvx/dotenvx/pull/908))
6
12
 
7
13
  ## [2.14.0](https://github.com/dotenvx/dotenvx/compare/v2.13.0...v2.14.0) (2026-07-16)
8
14
 
package/README.md CHANGED
@@ -153,6 +153,19 @@ Hello [REDACTED]
153
153
 
154
154
  see [Codex redaction guide](https://dotenvx.com/docs/cli/run-redact-codex-exec)
155
155
 
156
+ </details>
157
+ <details><summary>1Password 🔐</summary><br>
158
+
159
+ Run with secrets resolved directly from 1Password.
160
+
161
+ ```sh
162
+ $ echo "HELLO=op://Personal/hello/password" > .env
163
+ $ dotenvx run -- sh -c 'echo Hello $HELLO'
164
+ Hello World
165
+ ```
166
+
167
+ see [1Password guide](https://dotenvx.com/docs/secrets-in-1password)
168
+
156
169
  </details>
157
170
  <details><summary>TypeScript 📘</summary><br>
158
171
 
@@ -1156,6 +1169,30 @@ $ dotenvx get GOODBYE
1156
1169
  [MISSING_KEY] missing key (GOODBYE)
1157
1170
  ```
1158
1171
 
1172
+ </details>
1173
+ <details><summary>`run` - 1Password</summary><br>
1174
+
1175
+ Resolve [1Password op://](https://developer.1password.com/docs/cli/secrets-reference-syntax/) directly from your `.env` file.
1176
+
1177
+ ```ini
1178
+ # .env
1179
+ API_KEY=op://Personal/my_api_key/password
1180
+ ```
1181
+
1182
+ Install the [1Password CLI](https://developer.1password.com/docs/cli/get-started/) and authenticate with `op`. Dotenvx automatically reads `op://` values through `op read` before injecting them.
1183
+
1184
+ ```sh
1185
+ $ dotenvx run -- node index.js
1186
+ ```
1187
+
1188
+ Use `--no-1password` to leave `op://` values unresolved.
1189
+
1190
+ ```sh
1191
+ $ dotenvx run --no-1password -- node index.js
1192
+ ```
1193
+
1194
+ The same flag is available for `dotenvx get` and `dotenvx validate`.
1195
+
1159
1196
  </details>
1160
1197
  <details><summary>`run -f <directory>`</summary><br>
1161
1198
 
@@ -1478,29 +1515,6 @@ $ dotenvx run --validate --strict -- node index.js
1478
1515
 
1479
1516
  Any inline comment containing the word `optional` marks that key as optional. If `.env.example` is missing, dotenvx reports `MISSING_ENV_EXAMPLE`. An empty `.env.example` is valid and declares no required variables.
1480
1517
 
1481
- </details>
1482
- <details><summary>`validate`</summary><br>
1483
-
1484
- Validate `.env` file(s) against `.env.example` without running a command.
1485
-
1486
- ```ini
1487
- # .env.example
1488
- DATABASE_URL=
1489
- API_KEY=
1490
- SENTRY_DSN= # optional
1491
- ```
1492
-
1493
- ```sh
1494
- $ dotenvx validate
1495
- [VALIDATION_FAILED] missing required (DATABASE_URL, API_KEY). fix: [https://github.com/dotenvx/dotenvx/issues/907]
1496
- ```
1497
-
1498
- Use `-f` and `-fk` to validate a specific env file and keys file. The command exits with code `1` when validation fails and prints errors to stderr. On success, it exits with code `0`.
1499
-
1500
- ```sh
1501
- $ dotenvx validate -f .env.production -fk .env.keys
1502
- ```
1503
-
1504
1518
  </details>
1505
1519
  <details><summary>`run --strict`</summary><br>
1506
1520
 
@@ -2490,6 +2504,29 @@ $ dotenvx ls --json
2490
2504
  $ dotenvx ls --json > dotenv-files.json
2491
2505
  ```
2492
2506
 
2507
+ </details>
2508
+ <details><summary>`validate`</summary><br>
2509
+
2510
+ Validate `.env` file(s) against `.env.example` without running a command.
2511
+
2512
+ ```ini
2513
+ # .env.example
2514
+ DATABASE_URL=
2515
+ API_KEY=
2516
+ SENTRY_DSN= # optional
2517
+ ```
2518
+
2519
+ ```sh
2520
+ $ dotenvx validate
2521
+ [VALIDATION_FAILED] missing required (DATABASE_URL, API_KEY). fix: [https://github.com/dotenvx/dotenvx/issues/907]
2522
+ ```
2523
+
2524
+ Use `-f` and `-fk` to validate a specific env file and keys file. The command exits with code `1` when validation fails and prints errors to stderr. On success, it exits with code `0`.
2525
+
2526
+ ```sh
2527
+ $ dotenvx validate -f .env.production -fk .env.keys
2528
+ ```
2529
+
2493
2530
  </details>
2494
2531
  <details><summary>`genexample`</summary><br>
2495
2532
 
@@ -3225,6 +3262,30 @@ Turn off [Dotenvx Armor ⛨](https://dotenvx.com/armor) features.
3225
3262
  require('@dotenvx/dotenvx').config({noArmor: true})
3226
3263
  ```
3227
3264
 
3265
+ </details>
3266
+ <details><summary>`config(no1Password:)` - no1Password</summary><br>
3267
+
3268
+ By default, `config()` automatically resolves `op://` values through the installed [1Password CLI](https://developer.1password.com/docs/cli/get-started/).
3269
+
3270
+ ```ini
3271
+ # .env
3272
+ API_KEY=op://Personal/my_api_key/password
3273
+ ```
3274
+
3275
+ ```js
3276
+ // index.js
3277
+ require('@dotenvx/dotenvx').config()
3278
+
3279
+ console.log(process.env.API_KEY)
3280
+ ```
3281
+
3282
+ Set `no1Password` to leave `op://` values unresolved and avoid calling `op`.
3283
+
3284
+ ```js
3285
+ // index.js
3286
+ require('@dotenvx/dotenvx').config({no1Password: true})
3287
+ ```
3288
+
3228
3289
  </details>
3229
3290
  <details><summary>`parse(src)`</summary><br>
3230
3291
 
@@ -3385,7 +3446,6 @@ Set `mask: 0` to fully mask values.
3385
3446
  * [GitHub Actions](https://dotenvx.com/docs/cis/github-actions)
3386
3447
  * [Password Managers](https://dotenvx.com/docs#password-managers)
3387
3448
  * [1Password](https://dotenvx.com/docs/guides/1password)
3388
- * [Bitwarden](https://dotenvx.com/docs/guides/bitwarden)
3389
3449
  * [Background Jobs](https://dotenvx.com/docs#background-jobs)
3390
3450
  * [Trigger.dev](https://dotenvx.com/docs/background-jobs/triggerdotdev)
3391
3451
  * [Package Managers](https://dotenvx.com/docs#package-managers)
package/package.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
- "version": "2.14.0",
2
+ "version": "2.15.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
6
6
  "keywords": [
7
7
  "dotenv",
8
- "env"
8
+ "env",
9
+ ".env",
10
+ "environment",
11
+ "variables",
12
+ "config",
13
+ "settings",
14
+ "env vars",
15
+ "environment variables",
16
+ "secret-management",
17
+ "secrets"
9
18
  ],
10
19
  "homepage": "https://github.com/dotenvx/dotenvx",
11
20
  "repository": {
@@ -38,6 +38,8 @@ async function get (key) {
38
38
  envKeysFile: resolveEnvKeysFile(options.envKeysFile),
39
39
  noArmor,
40
40
  noKeychain,
41
+ no1Password: options['1password'] === false || options.no1Password === true,
42
+ noBitwarden: options.bitwarden === false || options.noBitwarden === true,
41
43
  onStatus: (text) => {
42
44
  if (spinner && text) {
43
45
  spinner.text = text
@@ -118,6 +118,8 @@ async function run () {
118
118
  envKeysFile: resolveEnvKeysFile(options.envKeysFile),
119
119
  noArmor,
120
120
  noKeychain,
121
+ no1Password: options['1password'] === false || options.no1Password === true,
122
+ noBitwarden: options.bitwarden === false || options.noBitwarden === true,
121
123
  token: options.token,
122
124
  command: commandArgs,
123
125
  onStatus: (text) => {
@@ -37,6 +37,8 @@ async function validate () {
37
37
  envKeysFile: resolveEnvKeysFile(options.envKeysFile),
38
38
  noArmor,
39
39
  noKeychain,
40
+ no1Password: options['1password'] === false || options.no1Password === true,
41
+ noBitwarden: options.bitwarden === false || options.noBitwarden === true,
40
42
  token: options.token,
41
43
  onStatus: (text) => {
42
44
  if (spinner && text) {
@@ -74,6 +74,8 @@ program.command('run')
74
74
  .option('--mask [characters]', 'inject masked values, optionally setting visible characters')
75
75
  .option('--no-armor', 'disable Dotenvx Armor features')
76
76
  .option('--no-native', 'disable OS secret store features')
77
+ .option('--no-1password', 'disable 1Password secret reference resolution')
78
+ .option('--no-bitwarden', 'disable Bitwarden secret reference resolution')
77
79
  .action(function (...args) {
78
80
  this.envs = envs
79
81
  return require('./actions/run').apply(this, args)
@@ -98,6 +100,8 @@ program.command('get')
98
100
  .option('--format <type>', 'format of the output (json, shell, colon, eval, eval-export)', 'json')
99
101
  .option('--no-armor', 'disable Dotenvx Armor features')
100
102
  .option('--no-native', 'disable OS secret store features')
103
+ .option('--no-1password', 'disable 1Password secret reference resolution')
104
+ .option('--no-bitwarden', 'disable Bitwarden secret reference resolution')
101
105
  .action(function (...args) {
102
106
  this.envs = envs
103
107
  return require('./actions/get').apply(this, args)
@@ -183,6 +187,25 @@ program.command('ls')
183
187
  return require('./actions/ls').apply(this, args)
184
188
  })
185
189
 
190
+ // dotenvx validate
191
+ program.command('validate')
192
+ .description('validate .env file(s) against .env.example')
193
+ .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
194
+ .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
195
+ .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
196
+ .option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
197
+ .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
198
+ .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
199
+ .option('--token <token>', 'set Armor ⛨ token')
200
+ .option('--no-armor', 'disable Dotenvx Armor features')
201
+ .option('--no-native', 'disable OS secret store features')
202
+ .option('--no-1password', 'disable 1Password secret reference resolution')
203
+ .option('--no-bitwarden', 'disable Bitwarden secret reference resolution')
204
+ .action(function (...args) {
205
+ this.envs = envs
206
+ return require('./actions/validate').apply(this, args)
207
+ })
208
+
186
209
  // dotenvx gitignore
187
210
  program.command('gitignore')
188
211
  .description('append to .gitignore')
@@ -201,23 +224,6 @@ program.command('genexample')
201
224
  return require('./actions/ext/genexample').apply(this, args)
202
225
  })
203
226
 
204
- // dotenvx validate
205
- program.command('validate')
206
- .description('validate .env file(s) against .env.example')
207
- .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
208
- .option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
209
- .option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
210
- .option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
211
- .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
212
- .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
213
- .option('--token <token>', 'set Armor ⛨ token')
214
- .option('--no-armor', 'disable Dotenvx Armor features')
215
- .option('--no-native', 'disable OS secret store features')
216
- .action(function (...args) {
217
- this.envs = envs
218
- return require('./actions/validate').apply(this, args)
219
- })
220
-
221
227
  // dotenvx precommit
222
228
  program.command('precommit')
223
229
  .description('prevent committing .env files to code')
@@ -1,5 +1,6 @@
1
1
  const FRAMES = ['◇', '⬖', '◆', '⬗']
2
2
  const FRAME_INTERVAL_MS = 80
3
+ let activeSpinner
3
4
 
4
5
  async function createSpinner (options = {}) {
5
6
  const stream = process.stderr
@@ -11,7 +12,7 @@ async function createSpinner (options = {}) {
11
12
  const frames = options.frames || FRAMES
12
13
 
13
14
  const { default: yoctoSpinner } = await import('yocto-spinner')
14
- return yoctoSpinner({
15
+ activeSpinner = yoctoSpinner({
15
16
  text,
16
17
  spinner: {
17
18
  frames,
@@ -19,6 +20,21 @@ async function createSpinner (options = {}) {
19
20
  },
20
21
  stream
21
22
  }).start()
23
+
24
+ return activeSpinner
25
+ }
26
+
27
+ createSpinner.stop = function () {
28
+ if (activeSpinner) activeSpinner.stop()
29
+ activeSpinner = null
30
+ }
31
+
32
+ createSpinner.pause = function () {
33
+ if (activeSpinner) activeSpinner.stop()
34
+ }
35
+
36
+ createSpinner.resume = function () {
37
+ if (activeSpinner) activeSpinner.start()
22
38
  }
23
39
 
24
40
  module.exports = createSpinner
@@ -9,6 +9,8 @@ const ISSUE_BY_CODE = {
9
9
  INVALID_PASSPHRASE: 'try again with the correct passphrase',
10
10
  INVALID_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/465',
11
11
  INVALID_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/756',
12
+ '1PASSWORD_FAILED': 'https://www.1password.dev/cli/get-started',
13
+ BITWARDEN_FAILED: 'https://bitwarden.com/help/cli/',
12
14
  MALFORMED_ENCRYPTED_DATA: 'https://github.com/dotenvx/dotenvx/issues/467',
13
15
  MISPAIRED_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/752',
14
16
  MISSING_DIRECTORY: 'https://github.com/dotenvx/dotenvx/issues/758',
@@ -100,6 +102,30 @@ class Errors {
100
102
  return e
101
103
  }
102
104
 
105
+ onePasswordFailed () {
106
+ const code = '1PASSWORD_FAILED'
107
+ const message = `[${code}] ${this.message}`
108
+ const help = `fix: [${ISSUE_BY_CODE[code]}]`
109
+
110
+ const e = new Error(message)
111
+ e.code = code
112
+ e.help = help
113
+ e.messageWithHelp = `${message}. ${help}`
114
+ return e
115
+ }
116
+
117
+ bitwardenFailed () {
118
+ const code = 'BITWARDEN_FAILED'
119
+ const message = `[${code}] ${this.message}`
120
+ const help = this.help || `fix: [${ISSUE_BY_CODE[code]}]`
121
+
122
+ const e = new Error(message)
123
+ e.code = code
124
+ e.help = help
125
+ e.messageWithHelp = `${message}. ${help}`
126
+ return e
127
+ }
128
+
103
129
  invalidColor () {
104
130
  const code = 'INVALID_COLOR'
105
131
  const message = `[${code}] Invalid color ${this.color}`
@@ -0,0 +1,149 @@
1
+ const { execFile, execFileSync } = require('child_process')
2
+ const Errors = require('./errors')
3
+ const prompts = require('./prompts')
4
+ const createSpinner = require('./createSpinner')
5
+
6
+ const FIELDS = new Set(['username', 'password', 'uri'])
7
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
8
+
9
+ function execFileAsync (command, args, options) {
10
+ return new Promise((resolve, reject) => {
11
+ execFile(command, args, options, (error, stdout) => {
12
+ if (error) return reject(error)
13
+ resolve(stdout)
14
+ })
15
+ })
16
+ }
17
+
18
+ function isSecretReference (value) {
19
+ return typeof value === 'string' && value.startsWith('bw://')
20
+ }
21
+
22
+ function parseSecretReference (value) {
23
+ const [itemId, field, ...extra] = value.slice('bw://'.length).split('/')
24
+
25
+ if (!UUID.test(itemId) || !field || extra.length > 0) {
26
+ throw new Error('invalid Bitwarden Password Manager reference')
27
+ }
28
+
29
+ if (!FIELDS.has(field)) {
30
+ throw new Error(`unsupported Bitwarden Password Manager field ${field}`)
31
+ }
32
+
33
+ return { itemId, field }
34
+ }
35
+
36
+ async function session (options) {
37
+ if (options.session) return options.session
38
+
39
+ if (process.stdin.isTTY && process.stderr.isTTY) {
40
+ createSpinner.pause()
41
+ const password = await prompts.password({
42
+ message: 'Bitwarden master password',
43
+ prefix: '◇',
44
+ separator: '='
45
+ }, {
46
+ input: process.stdin,
47
+ output: process.stderr
48
+ })
49
+ createSpinner.resume()
50
+ const passwordEnv = 'DOTENVX_BITWARDEN_PASSWORD'
51
+ options.session = secretValue(await execFileAsync('bw', ['unlock', '--passwordenv', passwordEnv, '--raw'], {
52
+ encoding: 'utf8',
53
+ windowsHide: true,
54
+ stdio: ['ignore', 'pipe', 'pipe'],
55
+ env: { ...process.env, [passwordEnv]: password }
56
+ }))
57
+ return options.session
58
+ }
59
+
60
+ if (!options.session) {
61
+ const error = new Error('Bitwarden Password Manager requires an unlocked BW_SESSION')
62
+ error.code = 'BW_SESSION_MISSING'
63
+ throw error
64
+ }
65
+ }
66
+
67
+ function resolutionError (key, error) {
68
+ let message
69
+ if (error && error.code === 'ENOENT') {
70
+ message = `Bitwarden Password Manager CLI is not installed and could not resolve ${key}`
71
+ } else if (error && error.code === 'BW_SESSION_MISSING') {
72
+ message = `Bitwarden Password Manager is locked and could not resolve ${key}; run 'export BW_SESSION="$(bw unlock --raw)"'`
73
+ } else if (error && error.message && error.message.startsWith('unsupported Bitwarden Password Manager field')) {
74
+ message = `${error.message} for ${key}`
75
+ } else if (error && error.message === 'invalid Bitwarden Password Manager reference') {
76
+ message = `invalid Bitwarden Password Manager reference for ${key}`
77
+ } else {
78
+ message = `Bitwarden Password Manager CLI failed to resolve ${key}`
79
+ }
80
+
81
+ return new Errors({
82
+ message,
83
+ help: 'fix: [https://bitwarden.com/help/cli/]'
84
+ }).bitwardenFailed()
85
+ }
86
+
87
+ function secretValue (stdout) {
88
+ return stdout.replace(/\r?\n$/, '')
89
+ }
90
+
91
+ async function resolveBitwardenPassword (parsed, options = {}) {
92
+ const errors = []
93
+ const unresolved = []
94
+ 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]
112
+ }
113
+ }
114
+
115
+ return { errors, unresolved }
116
+ }
117
+
118
+ function resolveBitwardenPasswordSync (parsed) {
119
+ const errors = []
120
+ const unresolved = []
121
+
122
+ for (const [key, value] of Object.entries(parsed)) {
123
+ if (!isSecretReference(value)) continue
124
+
125
+ try {
126
+ const { itemId, field } = parseSecretReference(value)
127
+ if (!process.env.BW_SESSION) {
128
+ const error = new Error('Bitwarden Password Manager requires an unlocked BW_SESSION')
129
+ error.code = 'BW_SESSION_MISSING'
130
+ throw error
131
+ }
132
+ const stdout = execFileSync('bw', ['get', field, itemId], {
133
+ encoding: 'utf8',
134
+ windowsHide: true,
135
+ stdio: ['ignore', 'pipe', 'pipe']
136
+ })
137
+ parsed[key] = secretValue(stdout)
138
+ } catch (error) {
139
+ errors.push(resolutionError(key, error))
140
+ unresolved.push(key)
141
+ delete parsed[key]
142
+ }
143
+ }
144
+
145
+ return { errors, unresolved }
146
+ }
147
+
148
+ module.exports = resolveBitwardenPassword
149
+ module.exports.sync = resolveBitwardenPasswordSync
@@ -0,0 +1,72 @@
1
+ const { execFile, execFileSync } = require('child_process')
2
+ const Errors = require('./errors')
3
+
4
+ function execFileAsync (command, args, options) {
5
+ return new Promise((resolve, reject) => {
6
+ execFile(command, args, options, (error, stdout) => {
7
+ if (error) return reject(error)
8
+ resolve(stdout)
9
+ })
10
+ })
11
+ }
12
+
13
+ function isSecretReference (value) {
14
+ return typeof value === 'string' && value.startsWith('op://')
15
+ }
16
+
17
+ function resolutionError (key, error) {
18
+ const message = error && error.code === 'ENOENT'
19
+ ? `1Password CLI is not installed and could not resolve ${key}`
20
+ : `1Password CLI failed to resolve ${key}`
21
+
22
+ return new Errors({ message }).onePasswordFailed()
23
+ }
24
+
25
+ async function resolveOnePassword (parsed) {
26
+ const errors = []
27
+ const unresolved = []
28
+
29
+ for (const [key, value] of Object.entries(parsed)) {
30
+ if (!isSecretReference(value)) continue
31
+
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]
42
+ }
43
+ }
44
+
45
+ return { errors, unresolved }
46
+ }
47
+
48
+ function resolveOnePasswordSync (parsed) {
49
+ const errors = []
50
+ const unresolved = []
51
+
52
+ for (const [key, value] of Object.entries(parsed)) {
53
+ if (!isSecretReference(value)) continue
54
+
55
+ try {
56
+ parsed[key] = execFileSync('op', ['read', value, '--no-newline'], {
57
+ encoding: 'utf8',
58
+ windowsHide: true,
59
+ stdio: ['ignore', 'pipe', 'pipe']
60
+ })
61
+ } catch (error) {
62
+ errors.push(resolutionError(key, error))
63
+ unresolved.push(key)
64
+ delete parsed[key]
65
+ }
66
+ }
67
+
68
+ return { errors, unresolved }
69
+ }
70
+
71
+ module.exports = resolveOnePassword
72
+ module.exports.sync = resolveOnePasswordSync
package/src/lib/main.d.ts CHANGED
@@ -194,6 +194,22 @@ export interface DotenvConfigOptions {
194
194
  */
195
195
  noNative?: boolean;
196
196
 
197
+ /**
198
+ * Turn off 1Password secret reference resolution.
199
+ *
200
+ * @default false
201
+ * @example require('@dotenvx/dotenvx').config({ no1Password: true })
202
+ */
203
+ no1Password?: boolean;
204
+
205
+ /**
206
+ * Turn off Bitwarden secret reference resolution.
207
+ *
208
+ * @default false
209
+ * @example require('@dotenvx/dotenvx').config({ noBitwarden: true })
210
+ */
211
+ noBitwarden?: boolean;
212
+
197
213
  }
198
214
 
199
215
  export type DotenvConfigEnv =
@@ -370,6 +386,22 @@ export interface GetOptions {
370
386
  */
371
387
  noNative?: boolean;
372
388
 
389
+ /**
390
+ * Turn off 1Password secret reference resolution.
391
+ *
392
+ * @default false
393
+ * @example require('@dotenvx/dotenvx').get('KEY', { no1Password: true })
394
+ */
395
+ no1Password?: boolean;
396
+
397
+ /**
398
+ * Turn off Bitwarden secret reference resolution.
399
+ *
400
+ * @default false
401
+ * @example require('@dotenvx/dotenvx').get('KEY', { noBitwarden: true })
402
+ */
403
+ noBitwarden?: boolean;
404
+
373
405
  }
374
406
 
375
407
  /**
package/src/lib/main.js CHANGED
@@ -87,6 +87,8 @@ const config = function (options = {}) {
87
87
  envKeysFile,
88
88
  noArmor,
89
89
  noKeychain,
90
+ no1Password: options.no1Password,
91
+ noBitwarden: options.noBitwarden,
90
92
  noSpinner: options.noSpinner,
91
93
  token: options.token
92
94
  })
@@ -331,7 +333,9 @@ const get = async function (key, options = {}) {
331
333
  all: options.all,
332
334
  envKeysFile: options.envKeysFile,
333
335
  noArmor,
334
- noKeychain
336
+ noKeychain,
337
+ no1Password: options.no1Password,
338
+ noBitwarden: options.noBitwarden
335
339
  })
336
340
 
337
341
  if (options.mask !== undefined) {
@@ -12,6 +12,8 @@ const keynames = require('./../conventions/keynames')
12
12
  const providers = require('./../providers')
13
13
  const decryptors = require('./../decryptors')
14
14
  const parseWithDecryptor = require('./../helpers/parseWithDecryptor')
15
+ const resolveOnePassword = require('./../helpers/resolveOnePassword')
16
+ const resolveBitwardenPassword = require('./../helpers/resolveBitwardenPassword')
15
17
 
16
18
  function unresolvedEncryptedErrors (parsed) {
17
19
  const keys = []
@@ -70,7 +72,7 @@ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, d
70
72
  return options
71
73
  }
72
74
 
73
- async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor }) {
75
+ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden }) {
74
76
  const row = {}
75
77
  row.type = TYPE_ENV
76
78
  row.string = env.value
@@ -101,6 +103,21 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
101
103
  row.injected = injected || {}
102
104
  row.existed = existed || {}
103
105
 
106
+ if (!no1Password) {
107
+ const result = await resolveOnePassword(row.injected)
108
+ row.errors.push(...result.errors)
109
+ for (const key of result.unresolved) delete row.parsed[key]
110
+ Object.assign(row.parsed, row.injected)
111
+ }
112
+
113
+ if (!noBitwarden) {
114
+ const passwordResult = await resolveBitwardenPassword(row.injected)
115
+ row.errors.push(...passwordResult.errors)
116
+ for (const key of passwordResult.unresolved) delete row.parsed[key]
117
+
118
+ Object.assign(row.parsed, row.injected)
119
+ }
120
+
104
121
  inject(processEnv, row.parsed)
105
122
  } catch (e) {
106
123
  row.errors = [e]
@@ -109,7 +126,7 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
109
126
  return row
110
127
  }
111
128
 
112
- function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor }) {
129
+ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden }) {
113
130
  const row = {}
114
131
  row.type = TYPE_ENV
115
132
  row.string = env.value
@@ -140,6 +157,21 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
140
157
  row.injected = injected || {}
141
158
  row.existed = existed || {}
142
159
 
160
+ if (!no1Password) {
161
+ const result = resolveOnePassword.sync(row.injected)
162
+ row.errors.push(...result.errors)
163
+ for (const key of result.unresolved) delete row.parsed[key]
164
+ Object.assign(row.parsed, row.injected)
165
+ }
166
+
167
+ if (!noBitwarden) {
168
+ const passwordResult = resolveBitwardenPassword.sync(row.injected)
169
+ row.errors.push(...passwordResult.errors)
170
+ for (const key of passwordResult.unresolved) delete row.parsed[key]
171
+
172
+ Object.assign(row.parsed, row.injected)
173
+ }
174
+
143
175
  inject(processEnv, row.parsed)
144
176
  } catch (e) {
145
177
  row.errors = [e]
@@ -148,7 +180,7 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
148
180
  return row
149
181
  }
150
182
 
151
- async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths }) {
183
+ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden }) {
152
184
  const row = {}
153
185
  row.type = TYPE_ENV_FILE
154
186
  row.filepath = env.value
@@ -182,6 +214,21 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
182
214
  row.errors = decryptErrors(parsed, errors)
183
215
  row.existed = existed || {}
184
216
 
217
+ if (!no1Password) {
218
+ const result = await resolveOnePassword(row.injected)
219
+ row.errors.push(...result.errors)
220
+ for (const key of result.unresolved) delete row.parsed[key]
221
+ Object.assign(row.parsed, row.injected)
222
+ }
223
+
224
+ if (!noBitwarden) {
225
+ const passwordResult = await resolveBitwardenPassword(row.injected)
226
+ row.errors.push(...passwordResult.errors)
227
+ for (const key of passwordResult.unresolved) delete row.parsed[key]
228
+
229
+ Object.assign(row.parsed, row.injected)
230
+ }
231
+
185
232
  inject(processEnv, parsed)
186
233
  } catch (e) {
187
234
  if (e.code === 'ENOENT' || e.code === 'EISDIR') {
@@ -194,7 +241,7 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
194
241
  return row
195
242
  }
196
243
 
197
- function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths }) {
244
+ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden }) {
198
245
  const row = {}
199
246
  row.type = TYPE_ENV_FILE
200
247
  row.filepath = env.value
@@ -228,6 +275,21 @@ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provid
228
275
  row.errors = decryptErrors(parsed, errors)
229
276
  row.existed = existed || {}
230
277
 
278
+ if (!no1Password) {
279
+ const result = resolveOnePassword.sync(row.injected)
280
+ row.errors.push(...result.errors)
281
+ for (const key of result.unresolved) delete row.parsed[key]
282
+ Object.assign(row.parsed, row.injected)
283
+ }
284
+
285
+ if (!noBitwarden) {
286
+ const passwordResult = resolveBitwardenPassword.sync(row.injected)
287
+ row.errors.push(...passwordResult.errors)
288
+ for (const key of passwordResult.unresolved) delete row.parsed[key]
289
+
290
+ Object.assign(row.parsed, row.injected)
291
+ }
292
+
231
293
  inject(processEnv, parsed)
232
294
  } catch (e) {
233
295
  if (e.code === 'ENOENT' || e.code === 'EISDIR') {
@@ -247,7 +309,6 @@ async function envs (options = {}) {
247
309
  const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
248
310
  const provider = await providers(options)
249
311
  const decryptor = await decryptors(options)
250
-
251
312
  for (const env of options.envs || []) {
252
313
  if (env.type === TYPE_ENV_FILE) {
253
314
  processedEnvs.push(await injectEnvFile({
@@ -257,7 +318,9 @@ async function envs (options = {}) {
257
318
  envKeysFilepath,
258
319
  provider,
259
320
  decryptor,
260
- readableFilepaths
321
+ readableFilepaths,
322
+ no1Password: options.no1Password,
323
+ noBitwarden: options.noBitwarden
261
324
  }))
262
325
  } else if (env.type === TYPE_ENV) {
263
326
  processedEnvs.push(await injectEnv({
@@ -266,7 +329,9 @@ async function envs (options = {}) {
266
329
  processEnv,
267
330
  envKeysFilepath,
268
331
  provider,
269
- decryptor
332
+ decryptor,
333
+ no1Password: options.no1Password,
334
+ noBitwarden: options.noBitwarden
270
335
  }))
271
336
  }
272
337
  }
@@ -294,7 +359,9 @@ function envsSync (options = {}) {
294
359
  envKeysFilepath,
295
360
  provider,
296
361
  decryptor,
297
- readableFilepaths
362
+ readableFilepaths,
363
+ no1Password: options.no1Password,
364
+ noBitwarden: options.noBitwarden
298
365
  }))
299
366
  } else if (env.type === TYPE_ENV) {
300
367
  processedEnvs.push(injectEnvSync({
@@ -303,7 +370,9 @@ function envsSync (options = {}) {
303
370
  processEnv,
304
371
  envKeysFilepath,
305
372
  provider,
306
- decryptor
373
+ decryptor,
374
+ no1Password: options.no1Password,
375
+ noBitwarden: options.noBitwarden
307
376
  }))
308
377
  }
309
378
  }
@@ -51,6 +51,8 @@ function buildOptions (options, processEnv) {
51
51
  envKeysFilepath: options.envKeysFilepath || options.envKeysFile || null,
52
52
  noArmor: options.noArmor,
53
53
  noKeychain: options.noKeychain,
54
+ no1Password: options.no1Password,
55
+ noBitwarden: options.noBitwarden,
54
56
  onStatus: options.onStatus
55
57
  }
56
58
  }