@dotenvx/dotenvx 2.2.2 → 2.3.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,27 @@
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.2.2...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.3.1...main)
6
+
7
+ ## [2.3.1](https://github.com/dotenvx/dotenvx/compare/v2.3.0...v2.3.1) (2026-07-08)
8
+
9
+ ### Changed
10
+
11
+ * Fix correct default valuation interpolation for empty strings by upgrading primitives package
12
+
13
+ ## [2.3.0](https://github.com/dotenvx/dotenvx/compare/v2.2.2...v2.3.0) (2026-07-08)
14
+
15
+ ### Added
16
+
17
+ * Add `lock up` and `lock down` commands for password protecting private keys ([#875](https://github.com/dotenvx/dotenvx/pull/875))
18
+
19
+ ### Changed
20
+
21
+ * BREAKING: Rename `keychain` commands to `native` to make way for windows and linux. ([#875](https://github.com/dotenvx/dotenvx/pull/875))
22
+
23
+ ### Removed
24
+
25
+ * BREAKING: Remove `--no-ops`. Use `--no-armor`. ([#875](https://github.com/dotenvx/dotenvx/pull/875))
6
26
 
7
27
  ## [2.2.2](https://github.com/dotenvx/dotenvx/compare/v2.2.1...v2.2.2) (2026-07-08)
8
28
 
package/README.md CHANGED
@@ -1385,12 +1385,12 @@ $ dotenvx run --token "$DOTENVX_ARMOR_TOKEN" -- yourcommand
1385
1385
  ```
1386
1386
 
1387
1387
  </details>
1388
- <details><summary>`run --no-keychain`</summary><br>
1388
+ <details><summary>`run --no-native`</summary><br>
1389
1389
 
1390
- Turn off macOS Keychain lookups.
1390
+ Turn off OS secret store lookups.
1391
1391
 
1392
1392
  ```sh
1393
- $ dotenvx run --no-keychain -- yourcommand
1393
+ $ dotenvx run --no-native -- yourcommand
1394
1394
  ```
1395
1395
 
1396
1396
  </details>
@@ -1415,12 +1415,12 @@ World
1415
1415
  ```
1416
1416
 
1417
1417
  </details>
1418
- <details><summary>`get KEY --no-keychain`</summary><br>
1418
+ <details><summary>`get KEY --no-native`</summary><br>
1419
1419
 
1420
- Turn off macOS Keychain lookups for get.
1420
+ Turn off OS secret store lookups for get.
1421
1421
 
1422
1422
  ```sh
1423
- $ dotenvx get HELLO --no-keychain
1423
+ $ dotenvx get HELLO --no-native
1424
1424
  ```
1425
1425
 
1426
1426
  </details>
@@ -1739,12 +1739,12 @@ set HELLO_PLAIN (.env)
1739
1739
  Keys ending in `_PLAIN` are not encrypted by `dotenvx set` or `dotenvx encrypt`.
1740
1740
 
1741
1741
  </details>
1742
- <details><summary>`set KEY value --no-keychain`</summary><br>
1742
+ <details><summary>`set KEY value --no-native`</summary><br>
1743
1743
 
1744
- Turn off macOS Keychain lookups for set.
1744
+ Turn off OS secret store lookups for set.
1745
1745
 
1746
1746
  ```sh
1747
- $ dotenvx set HELLO Dotenvx --no-keychain
1747
+ $ dotenvx set HELLO Dotenvx --no-native
1748
1748
  ```
1749
1749
 
1750
1750
  </details>
@@ -1779,12 +1779,12 @@ $ dotenvx encrypt -f .env.production
1779
1779
  ```
1780
1780
 
1781
1781
  </details>
1782
- <details><summary>`encrypt --no-keychain`</summary><br>
1782
+ <details><summary>`encrypt --no-native`</summary><br>
1783
1783
 
1784
- Turn off macOS Keychain lookups for encrypt.
1784
+ Turn off OS secret store lookups for encrypt.
1785
1785
 
1786
1786
  ```sh
1787
- $ dotenvx encrypt --no-keychain
1787
+ $ dotenvx encrypt --no-native
1788
1788
  ◈ encrypted (.env)
1789
1789
  ```
1790
1790
 
@@ -1918,12 +1918,12 @@ $ dotenvx decrypt
1918
1918
  ```
1919
1919
 
1920
1920
  </details>
1921
- <details><summary>`decrypt --no-keychain`</summary><br>
1921
+ <details><summary>`decrypt --no-native`</summary><br>
1922
1922
 
1923
- Turn off macOS Keychain lookups for decrypt.
1923
+ Turn off OS secret store lookups for decrypt.
1924
1924
 
1925
1925
  ```sh
1926
- $ dotenvx decrypt --no-keychain
1926
+ $ dotenvx decrypt --no-native
1927
1927
  ◇ decrypted (.env)
1928
1928
  ```
1929
1929
 
@@ -2039,12 +2039,12 @@ $ dotenvx keypair
2039
2039
  ```
2040
2040
 
2041
2041
  </details>
2042
- <details><summary>`keypair --no-keychain`</summary><br>
2042
+ <details><summary>`keypair --no-native`</summary><br>
2043
2043
 
2044
- Turn off macOS Keychain lookups for keypair.
2044
+ Turn off OS secret store lookups for keypair.
2045
2045
 
2046
2046
  ```sh
2047
- $ dotenvx keypair --no-keychain
2047
+ $ dotenvx keypair --no-native
2048
2048
  {"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}
2049
2049
  ```
2050
2050
 
@@ -2323,52 +2323,93 @@ CMD ["/usr/local/bin/dotenvx", "run", "--", "node", "apps/backend/index.js"]
2323
2323
  ```
2324
2324
 
2325
2325
  </details>
2326
- <details><summary>`keychain`</summary><br>
2326
+ <details><summary>`lock`</summary><br>
2327
2327
 
2328
- Store private keys in macOS Keychain to keep them out of `.env.keys`.
2328
+ Lock private keys with a local passphrase to keep them protected inside `.env.keys`.
2329
+
2330
+ ```
2331
+ # example
2332
+ DOTENV_PRIVATE_KEY=locked:02f5b97ad58b49ae324cd4e7937bc19b251d006b31cacf46f789eeaf03f923cedc:AZIPDxKqjPLiGl5b4CqVGbR3CIBDUcqHthGaoeWLoUvxbTHJkj3jGoGWGaxFSDUJGQUmWDaExRzxKpVydYF_7qiWr1ecqksOFho5t3EMwKbqX2-y-LZO9K3a4SJaYAjDJXpn3NwG4vAt1oLmGA
2333
+ ```
2334
+
2335
+ </details>
2336
+ <details><summary>`lock up`</summary><br>
2337
+
2338
+ Lock a private key in `.env.keys` with a local passphrase.
2339
+
2340
+ ```sh
2341
+ $ dotenvx lock up
2342
+ ```
2343
+
2344
+ Here's what a locked key looks like:
2345
+
2346
+ ```ini
2347
+ # .env.keys
2348
+ DOTENV_PRIVATE_KEY=locked:02f5b97ad58b49ae324cd4e7937bc19b251d006b31cacf46f789eeaf03f923cedc:AZIPDxKqjPLiGl5b4CqVGbR3CIBDUcqHthGaoeWLoUvxbTHJkj3jGoGWGaxFSDUJGQUmWDaExRzxKpVydYF_7qiWr1ecqksOFho5t3EMwKbqX2-y-LZO9K3a4SJaYAjDJXpn3NwG4vAt1oLmGA
2349
+ ```
2350
+
2351
+ Specify files with `-f` and `-fk`.
2352
+
2353
+ ```sh
2354
+ $ dotenvx lock up -f .env.production -fk .env.keys
2355
+ ```
2356
+
2357
+ </details>
2358
+ <details><summary>`lock down`</summary><br>
2359
+
2360
+ Unlock a private key in `.env.keys` with its local passphrase.
2361
+
2362
+ ```sh
2363
+ $ dotenvx lock down
2364
+ ```
2365
+
2366
+ </details>
2367
+ <details><summary>`native`</summary><br>
2368
+
2369
+ Move private keys into your OS secret store (macOS Keychain supported).
2329
2370
 
2330
2371
  Currently supported on macOS.
2331
2372
 
2332
2373
  </details>
2333
- <details><summary>`keychain up`</summary><br>
2374
+ <details><summary>`native up`</summary><br>
2334
2375
 
2335
- Move a private key from `.env.keys` into macOS Keychain.
2376
+ Move a private key from `.env.keys` into your OS secret store.
2336
2377
 
2337
2378
  ```sh
2338
- $ dotenvx keychain up
2379
+ $ dotenvx native up
2339
2380
  ```
2340
2381
 
2341
2382
  Specify files with `-f` and `-fk`.
2342
2383
 
2343
2384
  ```sh
2344
- $ dotenvx keychain up -f .env.production -fk .env.keys
2385
+ $ dotenvx native up -f .env.production -fk .env.keys
2345
2386
  ```
2346
2387
 
2347
2388
  </details>
2348
- <details><summary>`keychain down`</summary><br>
2389
+ <details><summary>`native down`</summary><br>
2349
2390
 
2350
- Move a private key from macOS Keychain back into `.env.keys`.
2391
+ Move a private key from your OS secret store back into `.env.keys`.
2351
2392
 
2352
2393
  ```sh
2353
- $ dotenvx keychain down
2394
+ $ dotenvx native down
2354
2395
  ```
2355
2396
 
2356
2397
  </details>
2357
- <details><summary>`keychain push`</summary><br>
2398
+ <details><summary>`native push`</summary><br>
2358
2399
 
2359
- Copy a private key from `.env.keys` into macOS Keychain.
2400
+ Copy a private key from `.env.keys` into your OS secret store.
2360
2401
 
2361
2402
  ```sh
2362
- $ dotenvx keychain push
2403
+ $ dotenvx native push
2363
2404
  ```
2364
2405
 
2365
2406
  </details>
2366
- <details><summary>`keychain pull`</summary><br>
2407
+ <details><summary>`native pull`</summary><br>
2367
2408
 
2368
- Copy a private key from macOS Keychain into `.env.keys`.
2409
+ Copy a private key from your OS secret store into `.env.keys`.
2369
2410
 
2370
2411
  ```sh
2371
- $ dotenvx keychain pull
2412
+ $ dotenvx native pull
2372
2413
  ```
2373
2414
 
2374
2415
  </details>
@@ -2488,7 +2529,8 @@ Commands:
2488
2529
  prevent including .env files in docker
2489
2530
 
2490
2531
  Professional Security:
2491
- keychain ⌥ move private keys into macOS Keychain
2532
+ lock ⊡ lock private keys with a local passphrase
2533
+ native ⌥ move private keys into your OS secret store (macOS Keychain supported)
2492
2534
  armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]
2493
2535
  ```
2494
2536
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.2",
2
+ "version": "2.3.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "funding": "https://dotenvx.com",
45
45
  "dependencies": {
46
- "@dotenvx/primitives": "^1.7.1",
46
+ "@dotenvx/primitives": "^1.7.2",
47
47
  "commander": "^11.1.0",
48
48
  "conf": "^10.2.0",
49
49
  "dotenv": "^17.4.2",
@@ -4,12 +4,11 @@ const { logger } = require('./../../shared/logger')
4
4
  const catchAndLog = require('../../lib/helpers/catchAndLog')
5
5
  const createSpinner = require('../../lib/helpers/createSpinner')
6
6
  const Session = require('../../db/session')
7
- const normalizeArmorAliases = require('./normalizeArmorAliases')
8
7
 
9
8
  const decryptTransform = require('./../../lib/transforms/decrypt')
10
9
 
11
10
  async function decrypt () {
12
- const options = normalizeArmorAliases(this.opts())
11
+ const options = this.opts()
13
12
  const spinner = await createSpinner({ ...options, text: 'decrypting' })
14
13
 
15
14
  logger.debug(`options: ${JSON.stringify(options)}`)
@@ -17,7 +16,7 @@ async function decrypt () {
17
16
  const envs = this.envs
18
17
  const sesh = new Session()
19
18
  const noArmor = options.armor === false || (await sesh.noArmor())
20
- const noKeychain = options.keychain === false || options.noKeychain === true
19
+ const noKeychain = options.native === false || options.noNative === true
21
20
 
22
21
  let errorCount = 0
23
22
 
@@ -6,10 +6,9 @@ const encryptTransform = require('./../../lib/transforms/encrypt')
6
6
  const catchAndLog = require('../../lib/helpers/catchAndLog')
7
7
  const createSpinner = require('../../lib/helpers/createSpinner')
8
8
  const Session = require('../../db/session')
9
- const normalizeArmorAliases = require('./normalizeArmorAliases')
10
9
 
11
10
  async function encryptAction () {
12
- const options = normalizeArmorAliases(this.opts())
11
+ const options = this.opts()
13
12
  const spinner = await createSpinner({ ...options, text: 'encrypting' })
14
13
  const sesh = new Session()
15
14
 
@@ -21,7 +20,7 @@ async function encryptAction () {
21
20
  const fk = options.envKeysFile || '.env.keys'
22
21
  const noCreate = options.create === false
23
22
  const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
24
- const noKeychain = options.keychain === false || options.noKeychain === true
23
+ const noKeychain = options.native === false || options.noNative === true
25
24
 
26
25
  let errorCount = 0
27
26
 
@@ -6,11 +6,10 @@ const catchAndLog = require('./../../lib/helpers/catchAndLog')
6
6
  const createSpinner = require('../../lib/helpers/createSpinner')
7
7
  const Session = require('../../db/session')
8
8
  const getResolver = require('./../../lib/resolvers/get')
9
- const normalizeArmorAliases = require('./normalizeArmorAliases')
10
9
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
11
10
 
12
11
  async function get (key) {
13
- const options = normalizeDotenvConfigConvention(normalizeArmorAliases(this.opts()))
12
+ const options = normalizeDotenvConfigConvention(this.opts())
14
13
  const spinner = await createSpinner({ ...options, text: 'decrypting' })
15
14
 
16
15
  logger.debug(`options: ${JSON.stringify(options)}`)
@@ -33,7 +32,7 @@ async function get (key) {
33
32
  try {
34
33
  const sesh = new Session()
35
34
  const noArmor = options.armor === false || (await sesh.noArmor())
36
- const noKeychain = options.keychain === false || options.noKeychain === true
35
+ const noKeychain = options.native === false || options.noNative === true
37
36
  const { parsed, errors } = await getResolver({
38
37
  key,
39
38
  envs,
@@ -5,7 +5,7 @@ const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
5
5
 
6
6
  async function up () {
7
7
  const options = this.opts()
8
- const spinner = await createSpinner({ ...options, text: 'keychaining' })
8
+ const spinner = await createSpinner({ ...options, text: 'storing' })
9
9
 
10
10
  logger.debug(`options: ${JSON.stringify(options)}`)
11
11
 
@@ -15,7 +15,7 @@ async function up () {
15
15
 
16
16
  if (spinner) spinner.stop()
17
17
  if (changed) {
18
- logger.success(`⌥ keychained (${keyDisplay})`)
18
+ logger.success(`⌥ stored (${keyDisplay})`)
19
19
  } else {
20
20
  logger.info(`○ no change (${keyDisplay})`)
21
21
  }
@@ -3,10 +3,9 @@ const { logger } = require('./../../shared/logger')
3
3
  const keypairResolver = require('./../../lib/resolvers/keypair')
4
4
  const catchAndLog = require('./../../lib/helpers/catchAndLog')
5
5
  const createSpinner = require('../../lib/helpers/createSpinner')
6
- const normalizeArmorAliases = require('./normalizeArmorAliases')
7
6
 
8
7
  async function keypair (key) {
9
- const options = normalizeArmorAliases(this.opts())
8
+ const options = this.opts()
10
9
  const spinner = await createSpinner({ ...options, text: 'retrieving' })
11
10
 
12
11
  logger.debug(`options: ${JSON.stringify(options)}`)
@@ -21,7 +20,7 @@ async function keypair (key) {
21
20
  envFile: options.envFile,
22
21
  envKeysFile: options.envKeysFile,
23
22
  armor: options.armor,
24
- noKeychain: options.keychain === false || options.noKeychain === true,
23
+ noKeychain: options.native === false || options.noNative === true,
25
24
  token: options.token,
26
25
  command: process.argv.slice(2),
27
26
  onStatus: (text) => {
@@ -0,0 +1,74 @@
1
+ const crypto = require('crypto')
2
+
3
+ const { logger } = require('../../../shared/logger')
4
+ const LockDown = require('./../../../lib/services/lockDown')
5
+ const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
6
+ const prompts = require('../../../lib/helpers/prompts')
7
+
8
+ function unlockedValue (lockedPrivateKey, passphrase) {
9
+ const parts = lockedPrivateKey.split(':')
10
+ const payload = Buffer.from(parts.slice(2).join(':'), 'base64url')
11
+ const version = payload.subarray(0, 1)[0]
12
+ const salt = payload.subarray(1, 17)
13
+ const iv = payload.subarray(17, 29)
14
+ const tag = payload.subarray(29, 45)
15
+ const ciphertext = payload.subarray(45)
16
+ const key = crypto.scryptSync(passphrase, salt, 32)
17
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
18
+
19
+ if (version !== 1) {
20
+ throw new Error('unsupported locked private key version')
21
+ }
22
+
23
+ decipher.setAuthTag(tag)
24
+
25
+ return Buffer.concat([
26
+ decipher.update(ciphertext),
27
+ decipher.final()
28
+ ]).toString('utf8')
29
+ }
30
+
31
+ async function down () {
32
+ const options = this.opts()
33
+ logger.debug(`options: ${JSON.stringify(options)}`)
34
+
35
+ try {
36
+ const lockDown = new LockDown(options.envFile, options.envKeysFile)
37
+ const plan = lockDown.plan()
38
+ let results = plan.alreadyUnlocked
39
+
40
+ if (plan.locked.length > 0) {
41
+ const passphrase = await prompts.password({
42
+ message: 'passphrase',
43
+ prefix: '⊡',
44
+ separator: '='
45
+ }, {
46
+ input: process.stdin,
47
+ output: process.stderr
48
+ })
49
+
50
+ results = lockDown.run(lockedPrivateKey => unlockedValue(lockedPrivateKey, passphrase)).results
51
+ }
52
+
53
+ for (const result of results) {
54
+ const keyDisplay = armoredKeyDisplay(result.publicKeyValue) || result.privateKeyName
55
+
56
+ if (result.changed) {
57
+ logger.success(`⊡ unlocked (${keyDisplay})`)
58
+ } else {
59
+ logger.info(`○ no change (${keyDisplay})`)
60
+ }
61
+ }
62
+ } catch (error) {
63
+ if (error.code === 'PROMPT_CANCELLED') {
64
+ process.exit(130)
65
+ return
66
+ }
67
+
68
+ logger.error(error.message)
69
+ process.exit(1)
70
+ }
71
+ }
72
+
73
+ module.exports = down
74
+ module.exports.unlockedValue = unlockedValue
@@ -0,0 +1,80 @@
1
+ const crypto = require('crypto')
2
+
3
+ const { logger } = require('../../../shared/logger')
4
+ const LockUp = require('./../../../lib/services/lockUp')
5
+ const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
6
+ const prompts = require('../../../lib/helpers/prompts')
7
+
8
+ function lockedValue (privateKey, passphrase, publicKey) {
9
+ const salt = crypto.randomBytes(16)
10
+ const iv = crypto.randomBytes(12)
11
+ const key = crypto.scryptSync(passphrase, salt, 32)
12
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
13
+ const ciphertext = Buffer.concat([
14
+ cipher.update(privateKey, 'utf8'),
15
+ cipher.final()
16
+ ])
17
+ const tag = cipher.getAuthTag()
18
+ const payload = Buffer.concat([
19
+ Buffer.from([1]),
20
+ salt,
21
+ iv,
22
+ tag,
23
+ ciphertext
24
+ ]).toString('base64url')
25
+
26
+ return `locked:${publicKey}:${payload}`
27
+ }
28
+
29
+ async function up () {
30
+ const options = this.opts()
31
+ logger.debug(`options: ${JSON.stringify(options)}`)
32
+
33
+ try {
34
+ const lockUp = new LockUp(options.envFile, options.envKeysFile)
35
+
36
+ const plan = lockUp.plan()
37
+ let results = plan.alreadyLocked.map(lockedPrivateKey => ({
38
+ changed: false,
39
+ privateKeyName: lockedPrivateKey.privateKeyName,
40
+ privateKeyValue: undefined,
41
+ lockedPrivateKeyValue: lockedPrivateKey.lockedPrivateKeyValue,
42
+ publicKeyValue: lockedPrivateKey.publicKeyValue,
43
+ alreadyLocked: true
44
+ }))
45
+
46
+ if (plan.matches.length > 0) {
47
+ const passphrase = await prompts.password({
48
+ message: 'passphrase',
49
+ prefix: '⊡',
50
+ separator: '='
51
+ }, {
52
+ input: process.stdin,
53
+ output: process.stderr
54
+ })
55
+
56
+ results = lockUp.run((privateKey, publicKey) => lockedValue(privateKey, passphrase, publicKey)).results
57
+ }
58
+
59
+ for (const result of results) {
60
+ const keyDisplay = armoredKeyDisplay(result.publicKeyValue) || result.privateKeyName
61
+
62
+ if (result.changed) {
63
+ logger.success(`⊡ locked (${keyDisplay})`)
64
+ } else {
65
+ logger.info(`○ no change (${keyDisplay})`)
66
+ }
67
+ }
68
+ } catch (error) {
69
+ if (error.code === 'PROMPT_CANCELLED') {
70
+ process.exit(130)
71
+ return
72
+ }
73
+
74
+ logger.error(error.message)
75
+ process.exit(1)
76
+ }
77
+ }
78
+
79
+ module.exports = up
80
+ module.exports.lockedValue = lockedValue
@@ -6,7 +6,6 @@ const envsResolver = require('./../../lib/resolvers/envs')
6
6
  const catchAndLog = require('./../../lib/helpers/catchAndLog')
7
7
  const createSpinner = require('../../lib/helpers/createSpinner')
8
8
  const Session = require('../../db/session')
9
- const normalizeArmorAliases = require('./normalizeArmorAliases')
10
9
  const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvConfigQuiet')
11
10
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
12
11
 
@@ -47,7 +46,7 @@ function uniqueInjectedKeys (processedEnvs) {
47
46
  }
48
47
 
49
48
  async function run () {
50
- const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(normalizeArmorAliases(this.opts())))
49
+ const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
51
50
 
52
51
  let commandArgs = this.args
53
52
  if (commandArgs.length < 1) {
@@ -63,7 +62,7 @@ async function run () {
63
62
 
64
63
  const sesh = new Session()
65
64
  const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
66
- const noKeychain = options.keychain === false || options.noKeychain === true
65
+ const noKeychain = options.native === false || options.noNative === true
67
66
 
68
67
  if (commandArgs.length < 1) {
69
68
  if (spinner) spinner.stop()
@@ -8,10 +8,9 @@ const createSpinner = require('../../lib/helpers/createSpinner')
8
8
  const Errors = require('../../lib/helpers/errors')
9
9
  const prompts = require('../../lib/helpers/prompts')
10
10
  const Session = require('../../db/session')
11
- const normalizeArmorAliases = require('./normalizeArmorAliases')
12
11
 
13
12
  async function set (key, value) {
14
- const options = normalizeArmorAliases(this.opts())
13
+ const options = this.opts()
15
14
 
16
15
  let encrypt = true
17
16
  let settingMessage = 'encrypting'
@@ -65,7 +64,7 @@ async function set (key, value) {
65
64
  const fk = options.envKeysFile || '.env.keys'
66
65
  const noCreate = options.create === false
67
66
  const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
68
- const noKeychain = options.keychain === false || options.noKeychain === true
67
+ const noKeychain = options.native === false || options.noNative === true
69
68
 
70
69
  let errorCount = 0
71
70
 
@@ -3,7 +3,7 @@ const Session = require('./../../db/session')
3
3
 
4
4
  function configureArmorCommand (armor) {
5
5
  armor
6
- .description('move private keys off-device')
6
+ .description('move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
7
7
  .allowUnknownOption()
8
8
  .argument('[command]', 'dotenvx-armor command')
9
9
  .argument('[args...]', 'dotenvx-armor command arguments')
@@ -0,0 +1,27 @@
1
+ function configureLockCommand (lock) {
2
+ lock
3
+ .description('lock private keys with a local passphrase')
4
+ .action(function () {
5
+ this.help()
6
+ })
7
+
8
+ const upAction = require('./../actions/lock/up')
9
+ lock
10
+ .command('up')
11
+ .description('lock key in .env.keys')
12
+ .option('-f, --env-file <path>', 'path to your env file')
13
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
14
+ .action(upAction)
15
+
16
+ const downAction = require('./../actions/lock/down')
17
+ lock
18
+ .command('down')
19
+ .description('unlock key in .env.keys')
20
+ .option('-f, --env-file <path>', 'path to your env file')
21
+ .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
22
+ .action(downAction)
23
+
24
+ return lock
25
+ }
26
+
27
+ module.exports = configureLockCommand
@@ -1,43 +1,43 @@
1
- function configureKeychainCommand (keychain) {
2
- keychain
3
- .description('store private keys in macOS Keychain')
1
+ function configureNativeCommand (native) {
2
+ native
3
+ .description('move private keys into your OS secret store (macOS Keychain supported)')
4
4
  .action(function () {
5
5
  this.help()
6
6
  })
7
7
 
8
8
  const upAction = require('./../actions/keychain/up')
9
- keychain
9
+ native
10
10
  .command('up')
11
- .description('store key in macOS Keychain')
11
+ .description('store key in OS secret store')
12
12
  .option('-f, --env-file <path>', 'path to your env file')
13
13
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
14
14
  .action(upAction)
15
15
 
16
16
  const downAction = require('./../actions/keychain/down')
17
- keychain
17
+ native
18
18
  .command('down')
19
- .description('move key from macOS Keychain to .env.keys')
19
+ .description('move key from OS secret store to .env.keys')
20
20
  .option('-f, --env-file <path>', 'path to your env file')
21
21
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
22
22
  .action(downAction)
23
23
 
24
24
  const pushAction = require('./../actions/keychain/push')
25
- keychain
25
+ native
26
26
  .command('push')
27
- .description('push key to macOS Keychain from .env.keys')
27
+ .description('push key to OS secret store from .env.keys')
28
28
  .option('-f, --env-file <path>', 'path to your env file')
29
29
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
30
30
  .action(pushAction)
31
31
 
32
32
  const pullAction = require('./../actions/keychain/pull')
33
- keychain
33
+ native
34
34
  .command('pull')
35
- .description('pull key from macOS Keychain into .env.keys')
35
+ .description('pull key from OS secret store into .env.keys')
36
36
  .option('-f, --env-file <path>', 'path to your env file')
37
37
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
38
38
  .action(pullAction)
39
39
 
40
- return keychain
40
+ return native
41
41
  }
42
42
 
43
- module.exports = configureKeychainCommand
43
+ module.exports = configureNativeCommand
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /* c8 ignore start */
4
- const { Command, Option } = require('commander')
4
+ const { Command } = require('commander')
5
5
  const program = new Command()
6
6
 
7
7
  const { setLogLevel, logger } = require('../shared/logger')
@@ -73,8 +73,7 @@ program.command('run')
73
73
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
74
74
  .option('--token <token>', 'set Armor ⛨ token')
75
75
  .option('--no-armor', 'disable Dotenvx Armor features')
76
- .option('--no-keychain', 'disable macOS Keychain features')
77
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
76
+ .option('--no-native', 'disable OS secret store features')
78
77
  .action(function (...args) {
79
78
  this.envs = envs
80
79
  return require('./actions/run').apply(this, args)
@@ -97,8 +96,7 @@ program.command('get')
97
96
  .option('--pp', 'pretty print output (alias)')
98
97
  .option('--format <type>', 'format of the output (json, shell, colon, eval)', 'json')
99
98
  .option('--no-armor', 'disable Dotenvx Armor features')
100
- .option('--no-keychain', 'disable macOS Keychain features')
101
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
99
+ .option('--no-native', 'disable OS secret store features')
102
100
  .action(function (...args) {
103
101
  this.envs = envs
104
102
  return require('./actions/get').apply(this, args)
@@ -118,8 +116,7 @@ program.command('set')
118
116
  .option('-p, --plain', 'store value as plain text', false)
119
117
  .option('--no-create', 'do not create .env file(s) when missing')
120
118
  .option('--no-armor', 'disable Dotenvx Armor features')
121
- .option('--no-keychain', 'disable macOS Keychain features')
122
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
119
+ .option('--no-native', 'disable OS secret store features')
123
120
  .action(function (...args) {
124
121
  this.envs = envs
125
122
  return require('./actions/set').apply(this, args)
@@ -136,8 +133,7 @@ program.command('encrypt')
136
133
  .option('--token <token>', 'set Armor ⛨ token')
137
134
  .option('--no-create', 'do not create .env file(s) when missing')
138
135
  .option('--no-armor', 'disable Dotenvx Armor features')
139
- .option('--no-keychain', 'disable macOS Keychain features')
140
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
136
+ .option('--no-native', 'disable OS secret store features')
141
137
  .action(function (...args) {
142
138
  this.envs = envs
143
139
  return require('./actions/encrypt').apply(this, args)
@@ -151,8 +147,7 @@ program.command('decrypt')
151
147
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
152
148
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
153
149
  .option('--no-armor', 'disable Dotenvx Armor features')
154
- .option('--no-keychain', 'disable macOS Keychain features')
155
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
150
+ .option('--no-native', 'disable OS secret store features')
156
151
  .option('--stdout', 'send to stdout')
157
152
  .action(function (...args) {
158
153
  this.envs = envs
@@ -167,8 +162,7 @@ program.command('keypair')
167
162
  .option('-f, --env-file <path>', 'path(s) to your env file(s)')
168
163
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
169
164
  .option('--no-armor', 'disable Dotenvx Armor features')
170
- .option('--no-keychain', 'disable macOS Keychain features')
171
- .addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
165
+ .option('--no-native', 'disable OS secret store features')
172
166
  .option('-pp, --pretty-print', 'pretty print output')
173
167
  .option('--pp', 'pretty print output (alias)')
174
168
  .option('--format <type>', 'format of the output (json, shell, colon)', 'json')
@@ -268,11 +262,15 @@ program.command('help [command]')
268
262
  // dotenvx armor
269
263
  program.addHelpText('after', ' ')
270
264
  program.addHelpText('after', 'Professional Security: ')
271
- program.addHelpText('after', ' keychain ⌥ move private keys into macOS Keychain')
265
+ program.addHelpText('after', ' lock ⊡ lock private keys with a local passphrase')
266
+ program.addHelpText('after', ' native ⌥ move private keys into your OS secret store (macOS Keychain supported)')
272
267
  program.addHelpText('after', ' armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
273
268
 
274
- // dotenvx keychain
275
- require('./commands/keychain')(program.command('keychain', { hidden: true }))
269
+ // dotenvx native
270
+ require('./commands/native')(program.command('native', { hidden: true }))
271
+
272
+ // dotenvx lock
273
+ require('./commands/lock')(program.command('lock', { hidden: true }))
276
274
 
277
275
  // dotenvx armor
278
276
  require('./commands/armor')(program.command('armor', { hidden: true }))
@@ -7,6 +7,7 @@ const ISSUE_BY_CODE = {
7
7
  DANGEROUS_DEPENDENCY_HOIST: 'https://github.com/dotenvx/dotenvx/issues/622',
8
8
  INVALID_COLOR: 'must be 256 colors',
9
9
  INVALID_CONVENTION: 'https://github.com/dotenvx/dotenvx/issues/761',
10
+ INVALID_PASSPHRASE: 'try again with the correct passphrase',
10
11
  INVALID_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/465',
11
12
  INVALID_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/756',
12
13
  MALFORMED_ENCRYPTED_DATA: 'https://github.com/dotenvx/dotenvx/issues/467',
@@ -157,6 +158,18 @@ class Errors {
157
158
  return e
158
159
  }
159
160
 
161
+ invalidPassphrase () {
162
+ const code = 'INVALID_PASSPHRASE'
163
+ const message = `[${code}] could not unlock ${this.privateKeyName || 'DOTENV_PRIVATE_KEY'} using passphrase`
164
+ const help = `fix: [${ISSUE_BY_CODE[code]}]`
165
+
166
+ const e = new Error(message)
167
+ e.code = code
168
+ e.help = help
169
+ e.messageWithHelp = `${message}. ${help}`
170
+ return e
171
+ }
172
+
160
173
  malformedEncryptedData () {
161
174
  const code = 'MALFORMED_ENCRYPTED_DATA'
162
175
  const message = `[${code}] could not decrypt ${this.key} because encrypted data appears malformed`
@@ -42,20 +42,6 @@ function armorBanner () {
42
42
  }
43
43
 
44
44
  function dynamicAttempts (command, forwardedArgs) {
45
- if (command === 'vlt') {
46
- return [
47
- ['dotenvx-armor', forwardedArgs],
48
- ['dotenvx-ops', forwardedArgs]
49
- ]
50
- }
51
-
52
- if (command === 'ops') {
53
- return [
54
- ['dotenvx-armor', forwardedArgs],
55
- ['dotenvx-ops', forwardedArgs]
56
- ]
57
- }
58
-
59
45
  return [[`dotenvx-${command}`, forwardedArgs]]
60
46
  }
61
47
 
@@ -85,11 +71,7 @@ function executeDynamic (program, command, rawArgs) {
85
71
  }
86
72
 
87
73
  if (result.error) {
88
- if (command === 'vlt') {
89
- console.log(armorBanner())
90
- } else if (command === 'ops') {
91
- console.log(armorBanner())
92
- } else if (command === 'armor') {
74
+ if (command === 'armor') {
93
75
  console.log(armorBanner())
94
76
  } else {
95
77
  logger.info(`error: unknown command '${command}'`)
package/src/lib/main.d.ts CHANGED
@@ -180,20 +180,12 @@ export interface DotenvConfigOptions {
180
180
  noArmor?: boolean;
181
181
 
182
182
  /**
183
- * Turn off macOS Keychain features.
183
+ * Turn off OS secret store features.
184
184
  *
185
185
  * @default false
186
- * @example require('@dotenvx/dotenvx').config({ noKeychain: true })
186
+ * @example require('@dotenvx/dotenvx').config({ noNative: true })
187
187
  */
188
- noKeychain?: boolean;
189
-
190
- /**
191
- * Turn off Dotenvx Armor features. Alias for `noArmor`.
192
- *
193
- * @default false
194
- * @example require('@dotenvx/dotenvx').config({ noOps: true })
195
- */
196
- noOps?: boolean;
188
+ noNative?: boolean;
197
189
 
198
190
  }
199
191
 
@@ -271,20 +263,12 @@ export interface SetOptions {
271
263
  noArmor?: boolean;
272
264
 
273
265
  /**
274
- * Turn off macOS Keychain features.
266
+ * Turn off OS secret store features.
275
267
  *
276
268
  * @default false
277
- * @example require('@dotenvx/dotenvx').set(key, value, { noKeychain: true })
269
+ * @example require('@dotenvx/dotenvx').set(key, value, { noNative: true })
278
270
  */
279
- noKeychain?: boolean;
280
-
281
- /**
282
- * Turn off Dotenvx Armor features. Alias for `noArmor`.
283
- *
284
- * @default false
285
- * @example require('@dotenvx/dotenvx').set(key, value, { noOps: true })
286
- */
287
- noOps?: boolean;
271
+ noNative?: boolean;
288
272
 
289
273
  }
290
274
 
@@ -364,20 +348,12 @@ export interface GetOptions {
364
348
  noArmor?: boolean;
365
349
 
366
350
  /**
367
- * Turn off macOS Keychain features.
368
- *
369
- * @default false
370
- * @example require('@dotenvx/dotenvx').get('KEY', { noKeychain: true })
371
- */
372
- noKeychain?: boolean;
373
-
374
- /**
375
- * Turn off Dotenvx Armor features. Alias for `noArmor`.
351
+ * Turn off OS secret store features.
376
352
  *
377
353
  * @default false
378
- * @example require('@dotenvx/dotenvx').get('KEY', { noOps: true })
354
+ * @example require('@dotenvx/dotenvx').get('KEY', { noNative: true })
379
355
  */
380
- noOps?: boolean;
356
+ noNative?: boolean;
381
357
 
382
358
  }
383
359
 
package/src/lib/main.js CHANGED
@@ -380,11 +380,11 @@ const ls = function (directory, envFile, excludeEnvFile) {
380
380
 
381
381
  function resolveNoArmor (options = {}) {
382
382
  const sesh = new Session()
383
- return options.noArmor === true || options.noOps === true || (!options.token && sesh.noArmorSync())
383
+ return options.noArmor === true || (!options.token && sesh.noArmorSync())
384
384
  }
385
385
 
386
386
  function resolveNoKeychain (options = {}) {
387
- return options.noKeychain === true || options.keychain === false
387
+ return options.noNative === true || options.native === false
388
388
  }
389
389
 
390
390
  module.exports = {
@@ -395,7 +395,7 @@ module.exports = {
395
395
  set,
396
396
  get,
397
397
  ls,
398
- // expose for libs depending on @dotenvx/dotenvx - like dotenvx-ops
398
+ // expose for libs depending on @dotenvx/dotenvx
399
399
  setLogLevel,
400
400
  logger,
401
401
  getColor,
@@ -46,7 +46,7 @@ function armorProviderForOptions (options) {
46
46
  function useKeychain (options) {
47
47
  if (process.platform !== 'darwin') return false
48
48
  if (process.env.CI) return false
49
- return options.noKeychain !== true && options.keychain !== false
49
+ return options.noNative !== true && options.native !== false && options.noKeychain !== true
50
50
  }
51
51
 
52
52
  function useArmor (options, noArmor) {
@@ -29,7 +29,7 @@ class KeychainPull {
29
29
  try {
30
30
  privateKey = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
31
31
  } catch {
32
- throw new Error(`[NOT_FOUND] private key not found in macOS Keychain (${armoredKeyDisplay(publicKey)}). fix: [dotenvx keychain up]`)
32
+ throw new Error(`[NOT_FOUND] private key not found in macOS Keychain (${armoredKeyDisplay(publicKey)}). fix: [dotenvx native up]`)
33
33
  }
34
34
 
35
35
  const result = upsertEnvKey(privateKeyName, privateKey, envKeysFile)
@@ -0,0 +1,157 @@
1
+ const fs = require('fs')
2
+ const { derive, publickeys, scan } = require('@dotenvx/primitives')
3
+
4
+ const Errors = require('../helpers/errors')
5
+ const upsertEnvKey = require('../helpers/upsertEnvKey')
6
+
7
+ function latestValues (parsed) {
8
+ const result = {}
9
+
10
+ for (const [key, values] of Object.entries(parsed)) {
11
+ result[key] = values[values.length - 1]
12
+ }
13
+
14
+ return result
15
+ }
16
+
17
+ function lockedCandidates (keysSrc) {
18
+ const { parsed } = scan(keysSrc)
19
+ const values = latestValues(parsed)
20
+ const candidates = []
21
+
22
+ for (const [privateKeyName, privateKey] of Object.entries(values)) {
23
+ if (!privateKeyName.startsWith('DOTENV_PRIVATE_KEY')) continue
24
+ if (!privateKey.startsWith('locked:')) continue
25
+
26
+ const parts = privateKey.split(':')
27
+ if (parts.length < 3) continue
28
+
29
+ candidates.push({
30
+ privateKeyName,
31
+ lockedPrivateKeyValue: privateKey,
32
+ publicKeyValue: parts[1]
33
+ })
34
+ }
35
+
36
+ return candidates
37
+ }
38
+
39
+ function privateKeyring (keysSrc) {
40
+ const { parsed } = scan(keysSrc)
41
+ const values = latestValues(parsed)
42
+ const keyring = {}
43
+
44
+ for (const [privateKeyName, privateKey] of Object.entries(values)) {
45
+ if (!privateKeyName.startsWith('DOTENV_PRIVATE_KEY')) continue
46
+ if (privateKey.startsWith('locked:')) continue
47
+
48
+ try {
49
+ const publicKey = derive(privateKey)
50
+ keyring[publicKey] = { privateKeyName, privateKeyValue: privateKey }
51
+ } catch {}
52
+ }
53
+
54
+ return keyring
55
+ }
56
+
57
+ class LockDown {
58
+ constructor (envFile = '.env', envKeysFile = '.env.keys') {
59
+ this.envFile = envFile
60
+ this.envKeysFile = envKeysFile
61
+ }
62
+
63
+ lockedCandidates () {
64
+ const keysSrc = fs.readFileSync(this.envKeysFile, 'utf8')
65
+ const candidates = lockedCandidates(keysSrc)
66
+
67
+ if (candidates.length < 1) {
68
+ throw new Errors({ key: 'DOTENV_PRIVATE_KEY' }).missingKey()
69
+ }
70
+
71
+ return candidates
72
+ }
73
+
74
+ plan () {
75
+ const envSrc = fs.readFileSync(this.envFile, 'utf8')
76
+ const keysSrc = fs.readFileSync(this.envKeysFile, 'utf8')
77
+ const publicKeyValues = publickeys(envSrc)
78
+ const lockedByPublicKey = {}
79
+ const plaintextByPublicKey = privateKeyring(keysSrc)
80
+ const locked = []
81
+ const alreadyUnlocked = []
82
+
83
+ for (const lockedPrivateKey of lockedCandidates(keysSrc)) {
84
+ lockedByPublicKey[lockedPrivateKey.publicKeyValue] = lockedPrivateKey
85
+ }
86
+
87
+ for (const publicKey of publicKeyValues) {
88
+ const lockedPrivateKey = lockedByPublicKey[publicKey]
89
+ if (lockedPrivateKey) {
90
+ locked.push(lockedPrivateKey)
91
+ continue
92
+ }
93
+
94
+ const privateKey = plaintextByPublicKey[publicKey]
95
+ if (privateKey) {
96
+ alreadyUnlocked.push({
97
+ changed: false,
98
+ privateKeyName: privateKey.privateKeyName,
99
+ privateKeyValue: privateKey.privateKeyValue,
100
+ lockedPrivateKeyValue: undefined,
101
+ publicKeyValue: publicKey
102
+ })
103
+ }
104
+ }
105
+
106
+ if (locked.length < 1 && alreadyUnlocked.length < 1) {
107
+ throw new Errors({ key: 'DOTENV_PRIVATE_KEY' }).missingKey()
108
+ }
109
+
110
+ return { locked, alreadyUnlocked }
111
+ }
112
+
113
+ run (unlockPrivateKey) {
114
+ const { locked, alreadyUnlocked } = this.plan()
115
+ const results = [...alreadyUnlocked]
116
+
117
+ if (locked.length > 0 && !unlockPrivateKey) {
118
+ throw new Error('missing unlock private key function')
119
+ }
120
+
121
+ for (const lockedPrivateKey of locked) {
122
+ let privateKeyValue
123
+
124
+ try {
125
+ privateKeyValue = unlockPrivateKey(lockedPrivateKey.lockedPrivateKeyValue)
126
+ } catch (error) {
127
+ throw new Errors({ privateKeyName: lockedPrivateKey.privateKeyName }).invalidPassphrase()
128
+ }
129
+
130
+ const publicKeyValue = derive(privateKeyValue)
131
+
132
+ if (publicKeyValue !== lockedPrivateKey.publicKeyValue) continue
133
+
134
+ const result = upsertEnvKey(lockedPrivateKey.privateKeyName, privateKeyValue, this.envKeysFile)
135
+
136
+ results.push({
137
+ changed: result.changed,
138
+ privateKeyName: lockedPrivateKey.privateKeyName,
139
+ privateKeyValue,
140
+ lockedPrivateKeyValue: lockedPrivateKey.lockedPrivateKeyValue,
141
+ publicKeyValue
142
+ })
143
+ }
144
+
145
+ if (results.length < 1) {
146
+ throw new Errors({ key: 'DOTENV_PRIVATE_KEY' }).missingKey()
147
+ }
148
+
149
+ return {
150
+ changed: results.some(result => result.changed),
151
+ results
152
+ }
153
+ }
154
+ }
155
+
156
+ module.exports = LockDown
157
+ module.exports.lockedCandidates = lockedCandidates
@@ -0,0 +1,150 @@
1
+ const fs = require('fs')
2
+ const { derive, publickeys, scan } = require('@dotenvx/primitives')
3
+
4
+ const Errors = require('../helpers/errors')
5
+ const upsertEnvKey = require('../helpers/upsertEnvKey')
6
+
7
+ function latestValues (parsed) {
8
+ const result = {}
9
+
10
+ for (const [key, values] of Object.entries(parsed)) {
11
+ result[key] = values[values.length - 1]
12
+ }
13
+
14
+ return result
15
+ }
16
+
17
+ function privateKeyring (keysSrc) {
18
+ const { parsed } = scan(keysSrc)
19
+ const values = latestValues(parsed)
20
+ const keyring = {}
21
+
22
+ for (const [privateKeyName, privateKey] of Object.entries(values)) {
23
+ if (!privateKeyName.startsWith('DOTENV_PRIVATE_KEY')) continue
24
+
25
+ try {
26
+ const publicKey = derive(privateKey)
27
+ keyring[publicKey] = { privateKeyName, privateKeyValue: privateKey }
28
+ } catch {}
29
+ }
30
+
31
+ return keyring
32
+ }
33
+
34
+ function lockedKeyring (keysSrc) {
35
+ const { parsed } = scan(keysSrc)
36
+ const values = latestValues(parsed)
37
+ const keyring = {}
38
+
39
+ for (const [privateKeyName, privateKey] of Object.entries(values)) {
40
+ if (!privateKeyName.startsWith('DOTENV_PRIVATE_KEY')) continue
41
+ if (!privateKey.startsWith('locked:')) continue
42
+
43
+ const parts = privateKey.split(':')
44
+ if (parts.length < 3) continue
45
+
46
+ const publicKey = parts[1]
47
+ keyring[publicKey] = {
48
+ privateKeyName,
49
+ lockedPrivateKeyValue: privateKey,
50
+ publicKeyValue: publicKey
51
+ }
52
+ }
53
+
54
+ return keyring
55
+ }
56
+
57
+ class LockUp {
58
+ constructor (envFile = '.env', envKeysFile = '.env.keys') {
59
+ this.envFile = envFile
60
+ this.envKeysFile = envKeysFile
61
+ }
62
+
63
+ plan () {
64
+ const envFile = this.envFile
65
+ const envKeysFile = this.envKeysFile
66
+
67
+ const envSrc = fs.readFileSync(envFile, 'utf8')
68
+ const keysSrc = fs.readFileSync(envKeysFile, 'utf8')
69
+ const ring = privateKeyring(keysSrc)
70
+ const lockedRing = lockedKeyring(keysSrc)
71
+ const matches = []
72
+ const alreadyLocked = []
73
+
74
+ for (const publicKey of publickeys(envSrc)) {
75
+ const privateKey = ring[publicKey]
76
+ if (privateKey) {
77
+ matches.push({
78
+ privateKeyName: privateKey.privateKeyName,
79
+ privateKeyValue: privateKey.privateKeyValue,
80
+ publicKeyValue: publicKey
81
+ })
82
+ continue
83
+ }
84
+
85
+ const lockedPrivateKey = lockedRing[publicKey]
86
+ if (lockedPrivateKey) {
87
+ alreadyLocked.push(lockedPrivateKey)
88
+ }
89
+ }
90
+
91
+ if (matches.length < 1 && alreadyLocked.length < 1) {
92
+ throw new Errors({ key: 'DOTENV_PRIVATE_KEY' }).missingKey()
93
+ }
94
+
95
+ return { matches, alreadyLocked }
96
+ }
97
+
98
+ matches () {
99
+ const { matches } = this.plan()
100
+
101
+ if (matches.length < 1) {
102
+ throw new Errors({ key: 'DOTENV_PRIVATE_KEY' }).missingKey()
103
+ }
104
+
105
+ return matches
106
+ }
107
+
108
+ run (lockPrivateKey) {
109
+ if (!lockPrivateKey) {
110
+ throw new Error('missing lock private key function')
111
+ }
112
+
113
+ const envKeysFile = this.envKeysFile
114
+ const results = []
115
+
116
+ const { matches, alreadyLocked } = this.plan()
117
+
118
+ for (const privateKey of matches) {
119
+ const lockedPrivateKey = lockPrivateKey(privateKey.privateKeyValue, privateKey.publicKeyValue)
120
+ const result = upsertEnvKey(privateKey.privateKeyName, lockedPrivateKey, envKeysFile)
121
+
122
+ results.push({
123
+ changed: result.changed,
124
+ privateKeyName: privateKey.privateKeyName,
125
+ privateKeyValue: privateKey.privateKeyValue,
126
+ lockedPrivateKeyValue: lockedPrivateKey,
127
+ publicKeyValue: privateKey.publicKeyValue
128
+ })
129
+ }
130
+
131
+ for (const lockedPrivateKey of alreadyLocked) {
132
+ results.push({
133
+ changed: false,
134
+ privateKeyName: lockedPrivateKey.privateKeyName,
135
+ privateKeyValue: undefined,
136
+ lockedPrivateKeyValue: lockedPrivateKey.lockedPrivateKeyValue,
137
+ publicKeyValue: lockedPrivateKey.publicKeyValue,
138
+ alreadyLocked: true
139
+ })
140
+ }
141
+
142
+ return {
143
+ changed: results.some(result => result.changed),
144
+ results
145
+ }
146
+ }
147
+ }
148
+
149
+ module.exports = LockUp
150
+ module.exports.privateKeyring = privateKeyring
@@ -1,9 +0,0 @@
1
- function normalizeArmorAliases (options) {
2
- if (options.ops === false) {
3
- options.armor = false
4
- }
5
-
6
- return options
7
- }
8
-
9
- module.exports = normalizeArmorAliases