@dotenvx/dotenvx 2.1.4 → 2.2.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 +13 -1
- package/package.json +2 -2
- package/src/cli/actions/decrypt.js +3 -0
- package/src/cli/actions/encrypt.js +3 -2
- package/src/cli/actions/get.js +2 -0
- package/src/cli/actions/keychain/down.js +29 -0
- package/src/cli/actions/keychain/pull.js +29 -0
- package/src/cli/actions/keychain/push.js +29 -0
- package/src/cli/actions/keychain/up.js +29 -0
- package/src/cli/actions/keypair.js +1 -0
- package/src/cli/actions/run.js +2 -0
- package/src/cli/actions/set.js +2 -1
- package/src/cli/commands/keychain.js +43 -0
- package/src/cli/dotenvx.js +17 -7
- package/src/lib/main.d.ts +24 -0
- package/src/lib/main.js +11 -1
- package/src/lib/providers/index.js +76 -15
- package/src/lib/providers/keychain/index.js +20 -0
- package/src/lib/resolvers/envs.js +2 -2
- package/src/lib/resolvers/get.js +1 -0
- package/src/lib/services/keychainDown.js +57 -0
- package/src/lib/services/keychainPull.js +46 -0
- package/src/lib/services/keychainPush.js +63 -0
- package/src/lib/services/keychainUp.js +81 -0
- package/src/lib/transforms/decrypt.js +1 -1
- package/src/lib/transforms/set.js +3 -1
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.2.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.2.0](https://github.com/dotenvx/dotenvx/compare/v2.1.5...v2.2.0) (2026-07-07)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Use `dotenvx keychain up|down` to move your private keys to your macOS Keychain ([#872](https://github.com/dotenvx/dotenvx/pull/872))
|
|
12
|
+
|
|
13
|
+
## [2.1.5](https://github.com/dotenvx/dotenvx/compare/v2.1.4...v2.1.5) (2026-07-06)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* `precommit` and `prebuild` respect `_PLAIN` ([#871](https://github.com/dotenvx/dotenvx/pull/871))
|
|
6
18
|
|
|
7
19
|
## [2.1.4](https://github.com/dotenvx/dotenvx/compare/v2.1.3...v2.1.4) (2026-07-02)
|
|
8
20
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.2.0",
|
|
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.
|
|
46
|
+
"@dotenvx/primitives": "^1.7.1",
|
|
47
47
|
"commander": "^11.1.0",
|
|
48
48
|
"conf": "^10.2.0",
|
|
49
49
|
"dotenv": "^17.4.2",
|
|
@@ -17,6 +17,7 @@ async function decrypt () {
|
|
|
17
17
|
const envs = this.envs
|
|
18
18
|
const sesh = new Session()
|
|
19
19
|
const noArmor = options.armor === false || (await sesh.noArmor())
|
|
20
|
+
const noKeychain = options.keychain === false || options.noKeychain === true
|
|
20
21
|
|
|
21
22
|
let errorCount = 0
|
|
22
23
|
|
|
@@ -30,6 +31,7 @@ async function decrypt () {
|
|
|
30
31
|
ek: options.excludeKey,
|
|
31
32
|
fk: options.envKeysFile,
|
|
32
33
|
noArmor,
|
|
34
|
+
noKeychain,
|
|
33
35
|
onStatus: (text) => {
|
|
34
36
|
if (spinner && text) {
|
|
35
37
|
spinner.text = text
|
|
@@ -64,6 +66,7 @@ async function decrypt () {
|
|
|
64
66
|
ek: options.excludeKey,
|
|
65
67
|
fk: options.envKeysFile,
|
|
66
68
|
noArmor,
|
|
69
|
+
noKeychain,
|
|
67
70
|
onStatus: (text) => {
|
|
68
71
|
if (spinner && text) {
|
|
69
72
|
spinner.text = text
|
|
@@ -21,12 +21,13 @@ async function encryptAction () {
|
|
|
21
21
|
const fk = options.envKeysFile || '.env.keys'
|
|
22
22
|
const noCreate = options.create === false
|
|
23
23
|
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
24
|
+
const noKeychain = options.keychain === false || options.noKeychain === true
|
|
24
25
|
|
|
25
26
|
let errorCount = 0
|
|
26
27
|
|
|
27
28
|
// stdout - should not have a try so that exit codes can surface to stdout
|
|
28
29
|
if (options.stdout) {
|
|
29
|
-
const { processedEnvs } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate })
|
|
30
|
+
const { processedEnvs } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate, noKeychain })
|
|
30
31
|
|
|
31
32
|
if (spinner) spinner.stop()
|
|
32
33
|
for (const processedEnv of processedEnvs) {
|
|
@@ -47,7 +48,7 @@ async function encryptAction () {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
try {
|
|
50
|
-
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate })
|
|
51
|
+
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await encryptTransform({ envs, ik, ek, fk, noArmor, noCreate, noKeychain })
|
|
51
52
|
|
|
52
53
|
if (keysSrc) {
|
|
53
54
|
await fsx.writeFileX(fk, keysSrc)
|
package/src/cli/actions/get.js
CHANGED
|
@@ -33,6 +33,7 @@ async function get (key) {
|
|
|
33
33
|
try {
|
|
34
34
|
const sesh = new Session()
|
|
35
35
|
const noArmor = options.armor === false || (await sesh.noArmor())
|
|
36
|
+
const noKeychain = options.keychain === false || options.noKeychain === true
|
|
36
37
|
const { parsed, errors } = await getResolver({
|
|
37
38
|
key,
|
|
38
39
|
envs,
|
|
@@ -40,6 +41,7 @@ async function get (key) {
|
|
|
40
41
|
all: options.all,
|
|
41
42
|
envKeysFile: options.envKeysFile,
|
|
42
43
|
noArmor,
|
|
44
|
+
noKeychain,
|
|
43
45
|
onStatus: (text) => {
|
|
44
46
|
if (spinner && text) {
|
|
45
47
|
spinner.text = text
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const KeychainDown = require('./../../../lib/services/keychainDown')
|
|
3
|
+
const createSpinner = require('../../../lib/helpers/createSpinner')
|
|
4
|
+
const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
|
|
5
|
+
|
|
6
|
+
async function down () {
|
|
7
|
+
const options = this.opts()
|
|
8
|
+
const spinner = await createSpinner({ ...options, text: 'unkeychaining' })
|
|
9
|
+
|
|
10
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const { changed, privateKeyName, publicKeyValue } = new KeychainDown(options.envFile, options.envKeysFile).run()
|
|
14
|
+
const keyDisplay = armoredKeyDisplay(publicKeyValue) || privateKeyName
|
|
15
|
+
|
|
16
|
+
if (spinner) spinner.stop()
|
|
17
|
+
if (changed) {
|
|
18
|
+
logger.success(`◇ moved to .env.keys (${keyDisplay})`)
|
|
19
|
+
} else {
|
|
20
|
+
logger.info(`○ no change (${keyDisplay})`)
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (spinner) spinner.stop()
|
|
24
|
+
logger.error(error.message)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = down
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const KeychainPull = require('./../../../lib/services/keychainPull')
|
|
3
|
+
const createSpinner = require('../../../lib/helpers/createSpinner')
|
|
4
|
+
const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
|
|
5
|
+
|
|
6
|
+
async function pull () {
|
|
7
|
+
const options = this.opts()
|
|
8
|
+
const spinner = await createSpinner({ ...options, text: 'pulling' })
|
|
9
|
+
|
|
10
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const { changed, privateKeyName, publicKeyValue } = new KeychainPull(options.envFile, options.envKeysFile).run()
|
|
14
|
+
const keyDisplay = armoredKeyDisplay(publicKeyValue) || privateKeyName
|
|
15
|
+
|
|
16
|
+
if (spinner) spinner.stop()
|
|
17
|
+
if (changed) {
|
|
18
|
+
logger.success(`◇ pulled to .env.keys (${keyDisplay})`)
|
|
19
|
+
} else {
|
|
20
|
+
logger.info(`○ no change (${keyDisplay})`)
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (spinner) spinner.stop()
|
|
24
|
+
logger.error(error.message)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = pull
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const KeychainPush = require('./../../../lib/services/keychainPush')
|
|
3
|
+
const createSpinner = require('../../../lib/helpers/createSpinner')
|
|
4
|
+
const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
|
|
5
|
+
|
|
6
|
+
async function push () {
|
|
7
|
+
const options = this.opts()
|
|
8
|
+
const spinner = await createSpinner({ ...options, text: 'pushing' })
|
|
9
|
+
|
|
10
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const { changed, privateKeyName, publicKeyValue } = new KeychainPush(options.envFile, options.envKeysFile).run()
|
|
14
|
+
const keyDisplay = armoredKeyDisplay(publicKeyValue) || privateKeyName
|
|
15
|
+
|
|
16
|
+
if (spinner) spinner.stop()
|
|
17
|
+
if (changed) {
|
|
18
|
+
logger.success(`⌥ pushed (${keyDisplay})`)
|
|
19
|
+
} else {
|
|
20
|
+
logger.info(`○ no change (${keyDisplay})`)
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (spinner) spinner.stop()
|
|
24
|
+
logger.error(error.message)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = push
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { logger } = require('../../../shared/logger')
|
|
2
|
+
const KeychainUp = require('./../../../lib/services/keychainUp')
|
|
3
|
+
const createSpinner = require('../../../lib/helpers/createSpinner')
|
|
4
|
+
const armoredKeyDisplay = require('../../../lib/helpers/armoredKeyDisplay')
|
|
5
|
+
|
|
6
|
+
async function up () {
|
|
7
|
+
const options = this.opts()
|
|
8
|
+
const spinner = await createSpinner({ ...options, text: 'keychaining' })
|
|
9
|
+
|
|
10
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const { changed, privateKeyName, publicKeyValue } = new KeychainUp(options.envFile, options.envKeysFile).run()
|
|
14
|
+
const keyDisplay = armoredKeyDisplay(publicKeyValue) || privateKeyName
|
|
15
|
+
|
|
16
|
+
if (spinner) spinner.stop()
|
|
17
|
+
if (changed) {
|
|
18
|
+
logger.success(`⌥ keychained (${keyDisplay})`)
|
|
19
|
+
} else {
|
|
20
|
+
logger.info(`○ no change (${keyDisplay})`)
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (spinner) spinner.stop()
|
|
24
|
+
logger.error(error.message)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = up
|
|
@@ -21,6 +21,7 @@ async function keypair (key) {
|
|
|
21
21
|
envFile: options.envFile,
|
|
22
22
|
envKeysFile: options.envKeysFile,
|
|
23
23
|
armor: options.armor,
|
|
24
|
+
noKeychain: options.keychain === false || options.noKeychain === true,
|
|
24
25
|
token: options.token,
|
|
25
26
|
command: process.argv.slice(2),
|
|
26
27
|
onStatus: (text) => {
|
package/src/cli/actions/run.js
CHANGED
|
@@ -63,6 +63,7 @@ async function run () {
|
|
|
63
63
|
|
|
64
64
|
const sesh = new Session()
|
|
65
65
|
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
66
|
+
const noKeychain = options.keychain === false || options.noKeychain === true
|
|
66
67
|
|
|
67
68
|
if (commandArgs.length < 1) {
|
|
68
69
|
if (spinner) spinner.stop()
|
|
@@ -98,6 +99,7 @@ async function run () {
|
|
|
98
99
|
processEnv: process.env,
|
|
99
100
|
envKeysFile: options.envKeysFile,
|
|
100
101
|
noArmor,
|
|
102
|
+
noKeychain,
|
|
101
103
|
token: options.token,
|
|
102
104
|
command: commandArgs,
|
|
103
105
|
onStatus: (text) => {
|
package/src/cli/actions/set.js
CHANGED
|
@@ -65,11 +65,12 @@ async function set (key, value) {
|
|
|
65
65
|
const fk = options.envKeysFile || '.env.keys'
|
|
66
66
|
const noCreate = options.create === false
|
|
67
67
|
const noArmor = options.armor === false || (!options.token && (await sesh.noArmor()))
|
|
68
|
+
const noKeychain = options.keychain === false || options.noKeychain === true
|
|
68
69
|
|
|
69
70
|
let errorCount = 0
|
|
70
71
|
|
|
71
72
|
try {
|
|
72
|
-
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await setTransform({ envs, key, value, fk, noArmor, noCreate, encrypt })
|
|
73
|
+
const { keysSrc, processedEnvs, changedFilepaths, unchangedFilepaths } = await setTransform({ envs, key, value, fk, noArmor, noCreate, encrypt, noKeychain })
|
|
73
74
|
|
|
74
75
|
if (keysSrc) {
|
|
75
76
|
await fsx.writeFileX(fk, keysSrc)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function configureKeychainCommand (keychain) {
|
|
2
|
+
keychain
|
|
3
|
+
.description('store private keys in macOS Keychain')
|
|
4
|
+
.action(function () {
|
|
5
|
+
this.help()
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const upAction = require('./../actions/keychain/up')
|
|
9
|
+
keychain
|
|
10
|
+
.command('up')
|
|
11
|
+
.description('store key in macOS Keychain')
|
|
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/keychain/down')
|
|
17
|
+
keychain
|
|
18
|
+
.command('down')
|
|
19
|
+
.description('move key from macOS Keychain to .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
|
+
const pushAction = require('./../actions/keychain/push')
|
|
25
|
+
keychain
|
|
26
|
+
.command('push')
|
|
27
|
+
.description('push key to macOS Keychain from .env.keys')
|
|
28
|
+
.option('-f, --env-file <path>', 'path to your env file')
|
|
29
|
+
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
|
|
30
|
+
.action(pushAction)
|
|
31
|
+
|
|
32
|
+
const pullAction = require('./../actions/keychain/pull')
|
|
33
|
+
keychain
|
|
34
|
+
.command('pull')
|
|
35
|
+
.description('pull key from macOS Keychain into .env.keys')
|
|
36
|
+
.option('-f, --env-file <path>', 'path to your env file')
|
|
37
|
+
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file', '.env.keys')
|
|
38
|
+
.action(pullAction)
|
|
39
|
+
|
|
40
|
+
return keychain
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = configureKeychainCommand
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -72,7 +72,8 @@ program.command('run')
|
|
|
72
72
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
73
73
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
74
74
|
.option('--token <token>', 'set Armor ⛨ token')
|
|
75
|
-
.option('--no-armor', 'disable
|
|
75
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
76
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
76
77
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
77
78
|
.action(function (...args) {
|
|
78
79
|
this.envs = envs
|
|
@@ -95,7 +96,8 @@ program.command('get')
|
|
|
95
96
|
.option('-pp, --pretty-print', 'pretty print output')
|
|
96
97
|
.option('--pp', 'pretty print output (alias)')
|
|
97
98
|
.option('--format <type>', 'format of the output (json, shell, colon, eval)', 'json')
|
|
98
|
-
.option('--no-armor', 'disable
|
|
99
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
100
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
99
101
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
100
102
|
.action(function (...args) {
|
|
101
103
|
this.envs = envs
|
|
@@ -115,7 +117,8 @@ program.command('set')
|
|
|
115
117
|
.option('-c, --encrypt', 'encrypt value', true)
|
|
116
118
|
.option('-p, --plain', 'store value as plain text', false)
|
|
117
119
|
.option('--no-create', 'do not create .env file(s) when missing')
|
|
118
|
-
.option('--no-armor', 'disable
|
|
120
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
121
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
119
122
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
120
123
|
.action(function (...args) {
|
|
121
124
|
this.envs = envs
|
|
@@ -132,7 +135,8 @@ program.command('encrypt')
|
|
|
132
135
|
.option('--stdout', 'send to stdout')
|
|
133
136
|
.option('--token <token>', 'set Armor ⛨ token')
|
|
134
137
|
.option('--no-create', 'do not create .env file(s) when missing')
|
|
135
|
-
.option('--no-armor', 'disable
|
|
138
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
139
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
136
140
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
137
141
|
.action(function (...args) {
|
|
138
142
|
this.envs = envs
|
|
@@ -146,7 +150,8 @@ program.command('decrypt')
|
|
|
146
150
|
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
|
|
147
151
|
.option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
|
|
148
152
|
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
|
|
149
|
-
.option('--no-armor', 'disable
|
|
153
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
154
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
150
155
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
151
156
|
.option('--stdout', 'send to stdout')
|
|
152
157
|
.action(function (...args) {
|
|
@@ -161,7 +166,8 @@ program.command('keypair')
|
|
|
161
166
|
.argument('[KEY]', 'environment variable key name')
|
|
162
167
|
.option('-f, --env-file <path>', 'path(s) to your env file(s)')
|
|
163
168
|
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
|
|
164
|
-
.option('--no-armor', 'disable
|
|
169
|
+
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
170
|
+
.option('--no-keychain', 'disable macOS Keychain features')
|
|
165
171
|
.addOption(new Option('--no-ops', 'disable dotenvx-ops features (deprecated. use --no-armor)').hideHelp())
|
|
166
172
|
.option('-pp, --pretty-print', 'pretty print output')
|
|
167
173
|
.option('--pp', 'pretty print output (alias)')
|
|
@@ -255,7 +261,11 @@ program.addHelpText('after', ' ')
|
|
|
255
261
|
program.addHelpText('after', 'Professional Security: ')
|
|
256
262
|
program.addHelpText('after', ' login log in to move keys off-device, share with your team, and audit access')
|
|
257
263
|
program.addHelpText('after', ' logout log out of connected security features')
|
|
258
|
-
program.addHelpText('after', '
|
|
264
|
+
program.addHelpText('after', ' keychain ⌥ move private keys into macOS Keychain')
|
|
265
|
+
program.addHelpText('after', ' armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
|
|
266
|
+
|
|
267
|
+
// dotenvx keychain
|
|
268
|
+
require('./commands/keychain')(program.command('keychain', { hidden: true }))
|
|
259
269
|
|
|
260
270
|
// dotenvx armor
|
|
261
271
|
require('./commands/armor')(program.command('armor', { hidden: true }))
|
package/src/lib/main.d.ts
CHANGED
|
@@ -179,6 +179,14 @@ export interface DotenvConfigOptions {
|
|
|
179
179
|
*/
|
|
180
180
|
noArmor?: boolean;
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Turn off macOS Keychain features.
|
|
184
|
+
*
|
|
185
|
+
* @default false
|
|
186
|
+
* @example require('@dotenvx/dotenvx').config({ noKeychain: true })
|
|
187
|
+
*/
|
|
188
|
+
noKeychain?: boolean;
|
|
189
|
+
|
|
182
190
|
/**
|
|
183
191
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
184
192
|
*
|
|
@@ -262,6 +270,14 @@ export interface SetOptions {
|
|
|
262
270
|
*/
|
|
263
271
|
noArmor?: boolean;
|
|
264
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Turn off macOS Keychain features.
|
|
275
|
+
*
|
|
276
|
+
* @default false
|
|
277
|
+
* @example require('@dotenvx/dotenvx').set(key, value, { noKeychain: true })
|
|
278
|
+
*/
|
|
279
|
+
noKeychain?: boolean;
|
|
280
|
+
|
|
265
281
|
/**
|
|
266
282
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
267
283
|
*
|
|
@@ -347,6 +363,14 @@ export interface GetOptions {
|
|
|
347
363
|
*/
|
|
348
364
|
noArmor?: boolean;
|
|
349
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Turn off macOS Keychain features.
|
|
368
|
+
*
|
|
369
|
+
* @default false
|
|
370
|
+
* @example require('@dotenvx/dotenvx').get('KEY', { noKeychain: true })
|
|
371
|
+
*/
|
|
372
|
+
noKeychain?: boolean;
|
|
373
|
+
|
|
350
374
|
/**
|
|
351
375
|
* Turn off Dotenvx Armor features. Alias for `noArmor`.
|
|
352
376
|
*
|
package/src/lib/main.js
CHANGED
|
@@ -66,6 +66,7 @@ const config = function (options = {}) {
|
|
|
66
66
|
|
|
67
67
|
// dotenvx-armor related
|
|
68
68
|
const noArmor = resolveNoArmor(options)
|
|
69
|
+
const noKeychain = resolveNoKeychain(options)
|
|
69
70
|
|
|
70
71
|
try {
|
|
71
72
|
let envs = buildEnvs(options)
|
|
@@ -81,6 +82,7 @@ const config = function (options = {}) {
|
|
|
81
82
|
processEnv,
|
|
82
83
|
envKeysFile,
|
|
83
84
|
noArmor,
|
|
85
|
+
noKeychain,
|
|
84
86
|
noSpinner: options.noSpinner,
|
|
85
87
|
token: options.token
|
|
86
88
|
})
|
|
@@ -227,6 +229,7 @@ const set = async function (key, value, options = {}) {
|
|
|
227
229
|
const envKeysFilepath = options.envKeysFile
|
|
228
230
|
const noCreate = options.create === false
|
|
229
231
|
const noArmor = resolveNoArmor(options)
|
|
232
|
+
const noKeychain = resolveNoKeychain(options)
|
|
230
233
|
|
|
231
234
|
const {
|
|
232
235
|
keysSrc,
|
|
@@ -239,6 +242,7 @@ const set = async function (key, value, options = {}) {
|
|
|
239
242
|
value,
|
|
240
243
|
fk: envKeysFilepath,
|
|
241
244
|
noArmor,
|
|
245
|
+
noKeychain,
|
|
242
246
|
noCreate,
|
|
243
247
|
encrypt
|
|
244
248
|
})
|
|
@@ -306,6 +310,7 @@ const get = async function (key, options = {}) {
|
|
|
306
310
|
|
|
307
311
|
const envs = buildEnvs(options)
|
|
308
312
|
const noArmor = resolveNoArmor(options)
|
|
313
|
+
const noKeychain = resolveNoKeychain(options)
|
|
309
314
|
|
|
310
315
|
// ignore
|
|
311
316
|
const ignore = options.ignore || []
|
|
@@ -316,7 +321,8 @@ const get = async function (key, options = {}) {
|
|
|
316
321
|
overload: options.overload,
|
|
317
322
|
all: options.all,
|
|
318
323
|
envKeysFile: options.envKeysFile,
|
|
319
|
-
noArmor
|
|
324
|
+
noArmor,
|
|
325
|
+
noKeychain
|
|
320
326
|
})
|
|
321
327
|
|
|
322
328
|
for (const error of errors || []) {
|
|
@@ -377,6 +383,10 @@ function resolveNoArmor (options = {}) {
|
|
|
377
383
|
return options.noArmor === true || options.noOps === true || (!options.token && sesh.noArmorSync())
|
|
378
384
|
}
|
|
379
385
|
|
|
386
|
+
function resolveNoKeychain (options = {}) {
|
|
387
|
+
return options.noKeychain === true || options.keychain === false
|
|
388
|
+
}
|
|
389
|
+
|
|
380
390
|
module.exports = {
|
|
381
391
|
// dotenv proxies
|
|
382
392
|
config,
|
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
const Session = require('./../../db/session')
|
|
2
2
|
|
|
3
3
|
const armorProvider = require('./armor/index')
|
|
4
|
+
const keychainProvider = require('./keychain/index')
|
|
5
|
+
|
|
4
6
|
function syncArmorProvider (publicKeyHex) {
|
|
5
7
|
const { createSyncFn } = require('synckit')
|
|
6
8
|
const runProviderSync = createSyncFn(require.resolve('./worker.js'))
|
|
7
9
|
return runProviderSync(require.resolve('./armor/index'), publicKeyHex)
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
function hasKey (keyring, publicKeyHex) {
|
|
13
|
+
return keyring && keyring[publicKeyHex]
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
function composeProviders (providerFns) {
|
|
17
|
+
return async function provider (publicKeyHex) {
|
|
18
|
+
for (const providerFn of providerFns) {
|
|
19
|
+
const keyring = await providerFn(publicKeyHex)
|
|
20
|
+
if (hasKey(keyring, publicKeyHex)) return keyring
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {}
|
|
17
24
|
}
|
|
25
|
+
}
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
function composeProvidersSync (providerFns) {
|
|
28
|
+
return function providerSync (publicKeyHex) {
|
|
29
|
+
for (const providerFn of providerFns) {
|
|
30
|
+
const keyring = providerFn(publicKeyHex)
|
|
31
|
+
if (hasKey(keyring, publicKeyHex)) return keyring
|
|
32
|
+
}
|
|
22
33
|
|
|
34
|
+
return {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function armorProviderForOptions (options) {
|
|
23
39
|
return (publicKeyHex) => armorProvider(publicKeyHex, {
|
|
24
40
|
onStatus: options.onStatus,
|
|
25
41
|
token: options.token,
|
|
@@ -27,20 +43,65 @@ async function providers (options = {}) {
|
|
|
27
43
|
})
|
|
28
44
|
}
|
|
29
45
|
|
|
46
|
+
function useKeychain (options) {
|
|
47
|
+
if (process.platform !== 'darwin') return false
|
|
48
|
+
if (process.env.CI) return false
|
|
49
|
+
return options.noKeychain !== true && options.keychain !== false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function useArmor (options, noArmor) {
|
|
53
|
+
return options.noArmor !== true && options.armor !== false && !noArmor
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function providerFrom (providerFns, compose) {
|
|
57
|
+
if (providerFns.length === 0) return null
|
|
58
|
+
if (providerFns.length === 1) return providerFns[0]
|
|
59
|
+
|
|
60
|
+
return compose(providerFns)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function providers (options = {}) {
|
|
64
|
+
if (Object.prototype.hasOwnProperty.call(options, 'provider')) {
|
|
65
|
+
return options.provider
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const providerFns = []
|
|
69
|
+
|
|
70
|
+
if (useKeychain(options)) {
|
|
71
|
+
providerFns.push(keychainProvider)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.noArmor !== true && options.armor !== false) {
|
|
75
|
+
const sesh = new Session()
|
|
76
|
+
const noArmor = !options.token && await sesh.noArmor()
|
|
77
|
+
if (useArmor(options, noArmor)) {
|
|
78
|
+
providerFns.push(armorProviderForOptions(options))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return providerFrom(providerFns, composeProviders)
|
|
83
|
+
}
|
|
84
|
+
|
|
30
85
|
providers.sync = function providersSync (options = {}) {
|
|
31
86
|
if (Object.prototype.hasOwnProperty.call(options, 'provider')) {
|
|
32
87
|
return options.provider
|
|
33
88
|
}
|
|
34
89
|
|
|
35
|
-
|
|
36
|
-
|
|
90
|
+
const providerFns = []
|
|
91
|
+
|
|
92
|
+
if (useKeychain(options)) {
|
|
93
|
+
providerFns.push(keychainProvider)
|
|
37
94
|
}
|
|
38
95
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
96
|
+
if (options.noArmor !== true && options.armor !== false) {
|
|
97
|
+
const sesh = new Session()
|
|
98
|
+
const noArmor = !options.token && sesh.noArmorSync()
|
|
99
|
+
if (useArmor(options, noArmor)) {
|
|
100
|
+
providerFns.push(syncArmorProvider)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
42
103
|
|
|
43
|
-
return
|
|
104
|
+
return providerFrom(providerFns, composeProvidersSync)
|
|
44
105
|
}
|
|
45
106
|
|
|
46
107
|
module.exports = providers
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
4
|
+
const SERVICE = 'dotenvx'
|
|
5
|
+
|
|
6
|
+
function index (publicKeyHex) {
|
|
7
|
+
if (process.platform !== 'darwin') return {}
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const stdout = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKeyHex, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
11
|
+
const privateKeyHex = stdout.trim()
|
|
12
|
+
if (!privateKeyHex) return {}
|
|
13
|
+
|
|
14
|
+
return { [publicKeyHex]: privateKeyHex }
|
|
15
|
+
} catch {
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = index
|
|
@@ -235,7 +235,7 @@ async function envs (options = {}) {
|
|
|
235
235
|
const readableFilepaths = new Set()
|
|
236
236
|
const processEnv = options.processEnv || process.env
|
|
237
237
|
const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
|
|
238
|
-
const provider =
|
|
238
|
+
const provider = await providers(options)
|
|
239
239
|
|
|
240
240
|
for (const env of options.envs || []) {
|
|
241
241
|
if (env.type === TYPE_ENV_FILE) {
|
|
@@ -269,7 +269,7 @@ function envsSync (options = {}) {
|
|
|
269
269
|
const readableFilepaths = new Set()
|
|
270
270
|
const processEnv = options.processEnv || process.env
|
|
271
271
|
const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
|
|
272
|
-
const provider =
|
|
272
|
+
const provider = providers.sync(options)
|
|
273
273
|
|
|
274
274
|
for (const env of options.envs || []) {
|
|
275
275
|
if (env.type === TYPE_ENV_FILE) {
|
package/src/lib/resolvers/get.js
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const keynames = require('../conventions/keynames')
|
|
4
|
+
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
|
+
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
|
+
|
|
7
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
8
|
+
const SERVICE = 'dotenvx'
|
|
9
|
+
|
|
10
|
+
class KeychainDown {
|
|
11
|
+
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
12
|
+
this.envFile = envFile
|
|
13
|
+
this.envKeysFile = envKeysFile
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
run () {
|
|
17
|
+
const envFile = this.envFile
|
|
18
|
+
const envKeysFile = this.envKeysFile
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
publicKeyName,
|
|
22
|
+
privateKeyName
|
|
23
|
+
} = keynames(envFile)
|
|
24
|
+
|
|
25
|
+
const publicKey = readEnvKey(publicKeyName, envFile, { strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
|
|
26
|
+
let privateKey
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
privateKey = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
30
|
+
} catch (error) {
|
|
31
|
+
privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: false })
|
|
32
|
+
|
|
33
|
+
if (privateKey) {
|
|
34
|
+
return {
|
|
35
|
+
changed: false,
|
|
36
|
+
privateKeyName,
|
|
37
|
+
privateKeyValue: privateKey,
|
|
38
|
+
publicKeyValue: publicKey
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
upsertEnvKey(privateKeyName, privateKey, envKeysFile)
|
|
46
|
+
execFileSync(SECURITY_BIN, ['delete-generic-password', '-s', SERVICE, '-a', publicKey], { stdio: 'ignore' })
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
changed: true,
|
|
50
|
+
privateKeyName,
|
|
51
|
+
privateKeyValue: privateKey,
|
|
52
|
+
publicKeyValue: publicKey
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = KeychainDown
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const keynames = require('../conventions/keynames')
|
|
4
|
+
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
|
+
const upsertEnvKey = require('../helpers/upsertEnvKey')
|
|
6
|
+
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
+
|
|
8
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
9
|
+
const SERVICE = 'dotenvx'
|
|
10
|
+
|
|
11
|
+
class KeychainPull {
|
|
12
|
+
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
13
|
+
this.envFile = envFile
|
|
14
|
+
this.envKeysFile = envKeysFile
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
run () {
|
|
18
|
+
const envFile = this.envFile
|
|
19
|
+
const envKeysFile = this.envKeysFile
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
publicKeyName,
|
|
23
|
+
privateKeyName
|
|
24
|
+
} = keynames(envFile)
|
|
25
|
+
|
|
26
|
+
const publicKey = readEnvKey(publicKeyName, envFile, { strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
|
|
27
|
+
let privateKey
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
privateKey = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
31
|
+
} catch {
|
|
32
|
+
throw new Error(`[NOT_FOUND] private key not found in macOS Keychain (${armoredKeyDisplay(publicKey)}). fix: [dotenvx keychain up]`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = upsertEnvKey(privateKeyName, privateKey, envKeysFile)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
changed: result.changed,
|
|
39
|
+
privateKeyName,
|
|
40
|
+
privateKeyValue: privateKey,
|
|
41
|
+
publicKeyValue: publicKey
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = KeychainPull
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const keynames = require('../conventions/keynames')
|
|
4
|
+
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
|
+
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
6
|
+
|
|
7
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
8
|
+
const SERVICE = 'dotenvx'
|
|
9
|
+
|
|
10
|
+
function addGenericPassword (publicKey, label, privateKey) {
|
|
11
|
+
try {
|
|
12
|
+
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error('failed to save private key to macOS Keychain')
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class KeychainPush {
|
|
19
|
+
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
20
|
+
this.envFile = envFile
|
|
21
|
+
this.envKeysFile = envKeysFile
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
run () {
|
|
25
|
+
const envFile = this.envFile
|
|
26
|
+
const envKeysFile = this.envKeysFile
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
publicKeyName,
|
|
30
|
+
privateKeyName
|
|
31
|
+
} = keynames(envFile)
|
|
32
|
+
|
|
33
|
+
const publicKey = readEnvKey(publicKeyName, envFile, { strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
|
|
34
|
+
const privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: true })
|
|
35
|
+
const label = `dotenvx (${armoredKeyDisplay(publicKey)})`
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const existingPrivateKey = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
39
|
+
|
|
40
|
+
if (existingPrivateKey === privateKey) {
|
|
41
|
+
return {
|
|
42
|
+
changed: false,
|
|
43
|
+
label,
|
|
44
|
+
privateKeyName,
|
|
45
|
+
privateKeyValue: privateKey,
|
|
46
|
+
publicKeyValue: publicKey
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
addGenericPassword(publicKey, label, privateKey)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
changed: true,
|
|
55
|
+
label,
|
|
56
|
+
privateKeyName,
|
|
57
|
+
privateKeyValue: privateKey,
|
|
58
|
+
publicKeyValue: publicKey
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = KeychainPush
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
const keynames = require('../conventions/keynames')
|
|
4
|
+
const readEnvKey = require('../helpers/readEnvKey')
|
|
5
|
+
const removeEnvKey = require('../helpers/removeEnvKey')
|
|
6
|
+
const armoredKeyDisplay = require('../helpers/armoredKeyDisplay')
|
|
7
|
+
|
|
8
|
+
const SECURITY_BIN = '/usr/bin/security'
|
|
9
|
+
const SERVICE = 'dotenvx'
|
|
10
|
+
|
|
11
|
+
function addGenericPassword (publicKey, label, privateKey) {
|
|
12
|
+
try {
|
|
13
|
+
execFileSync(SECURITY_BIN, ['add-generic-password', '-U', '-s', SERVICE, '-a', publicKey, '-l', label, '-w', privateKey], { stdio: 'ignore' })
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error('failed to save private key to macOS Keychain')
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class KeychainUp {
|
|
20
|
+
constructor (envFile = '.env', envKeysFile = '.env.keys') {
|
|
21
|
+
this.envFile = envFile
|
|
22
|
+
this.envKeysFile = envKeysFile
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
run () {
|
|
26
|
+
const envFile = this.envFile
|
|
27
|
+
const envKeysFile = this.envKeysFile
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
publicKeyName,
|
|
31
|
+
privateKeyName
|
|
32
|
+
} = keynames(envFile)
|
|
33
|
+
|
|
34
|
+
const publicKey = readEnvKey(publicKeyName, envFile, { strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
|
|
35
|
+
const label = `dotenvx (${armoredKeyDisplay(publicKey)})`
|
|
36
|
+
let existingPrivateKey
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
existingPrivateKey = execFileSync(SECURITY_BIN, ['find-generic-password', '-s', SERVICE, '-a', publicKey, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
40
|
+
} catch {}
|
|
41
|
+
|
|
42
|
+
const privateKey = readEnvKey(privateKeyName, envKeysFile, { strict: !existingPrivateKey })
|
|
43
|
+
|
|
44
|
+
if (existingPrivateKey) {
|
|
45
|
+
if (!privateKey) {
|
|
46
|
+
return {
|
|
47
|
+
changed: false,
|
|
48
|
+
label,
|
|
49
|
+
privateKeyName,
|
|
50
|
+
privateKeyValue: existingPrivateKey,
|
|
51
|
+
publicKeyValue: publicKey
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (existingPrivateKey === privateKey) {
|
|
56
|
+
const result = removeEnvKey(privateKeyName, envKeysFile)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
changed: result.changed,
|
|
60
|
+
label,
|
|
61
|
+
privateKeyName,
|
|
62
|
+
privateKeyValue: privateKey,
|
|
63
|
+
publicKeyValue: publicKey
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
addGenericPassword(publicKey, label, privateKey)
|
|
69
|
+
removeEnvKey(privateKeyName, envKeysFile)
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
changed: true,
|
|
73
|
+
label,
|
|
74
|
+
privateKeyName,
|
|
75
|
+
privateKeyValue: privateKey,
|
|
76
|
+
publicKeyValue: publicKey
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = KeychainUp
|
|
@@ -22,7 +22,7 @@ async function decrypt (options = {}) {
|
|
|
22
22
|
const ik = options.ik
|
|
23
23
|
const ek = options.ek
|
|
24
24
|
const fk = options.fk
|
|
25
|
-
const provider =
|
|
25
|
+
const provider = await providers(options)
|
|
26
26
|
|
|
27
27
|
const processedEnvs = []
|
|
28
28
|
const changedFilepaths = []
|
|
@@ -36,6 +36,7 @@ async function setTransform (options = {}) {
|
|
|
36
36
|
const value = options.value
|
|
37
37
|
const fk = options.fk || '.env.keys'
|
|
38
38
|
let noArmor = options.noArmor // key storage selector below
|
|
39
|
+
const noKeychain = options.noKeychain
|
|
39
40
|
const noCreate = options.noCreate
|
|
40
41
|
const noEncrypt = !options.encrypt || isPlainKey(key)
|
|
41
42
|
|
|
@@ -149,7 +150,8 @@ async function setTransform (options = {}) {
|
|
|
149
150
|
envs: [env],
|
|
150
151
|
all: true,
|
|
151
152
|
envKeysFile: fk,
|
|
152
|
-
noArmor
|
|
153
|
+
noArmor,
|
|
154
|
+
noKeychain
|
|
153
155
|
})
|
|
154
156
|
|
|
155
157
|
const before = parsed[key]
|